Home | History | Annotate | Download | only in bin
      1 #!/usr/bin/perl -w
      2 #
      3 #   Copyright (c) International Business Machines  Corp., 2002,2007
      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 Getopt::Long;
     55 use Digest::MD5 qw(md5_base64);
     56 
     57 
     58 # Constants
     59 our $lcov_version	= "LCOV version 1.7";
     60 our $lcov_url		= "http://ltp.sourceforge.net/coverage/lcov.php";
     61 our $gcov_tool		= "gcov";
     62 our $tool_name		= basename($0);
     63 
     64 our $GCOV_VERSION_3_4_0	= 0x30400;
     65 our $GCOV_VERSION_3_3_0	= 0x30300;
     66 our $GCNO_FUNCTION_TAG	= 0x01000000;
     67 our $GCNO_LINES_TAG	= 0x01450000;
     68 our $GCNO_FILE_MAGIC	= 0x67636e6f;
     69 our $BBG_FILE_MAGIC	= 0x67626267;
     70 
     71 our $COMPAT_HAMMER	= "hammer";
     72 
     73 our $ERROR_GCOV		= 0;
     74 our $ERROR_SOURCE	= 1;
     75 
     76 # Prototypes
     77 sub print_usage(*);
     78 sub gen_info($);
     79 sub process_dafile($);
     80 sub match_filename($@);
     81 sub solve_ambiguous_match($$$);
     82 sub split_filename($);
     83 sub solve_relative_path($$);
     84 sub get_dir($);
     85 sub read_gcov_header($);
     86 sub read_gcov_file($);
     87 sub read_bb_file($$);
     88 sub read_string(*$);
     89 sub read_gcno_file($$);
     90 sub read_gcno_string(*$);
     91 sub read_hammer_bbg_file($$);
     92 sub read_hammer_bbg_string(*$);
     93 sub unpack_int32($$);
     94 sub info(@);
     95 sub get_gcov_version();
     96 sub system_no_output($@);
     97 sub read_config($);
     98 sub apply_config($);
     99 sub gen_initial_info($);
    100 sub process_graphfile($);
    101 sub warn_handler($);
    102 sub die_handler($);
    103 
    104 # Global variables
    105 our $gcov_version;
    106 our $graph_file_extension;
    107 our $data_file_extension;
    108 our @data_directory;
    109 our $test_name = "";
    110 our $quiet;
    111 our $help;
    112 our $output_filename;
    113 our $base_directory;
    114 our $version;
    115 our $follow;
    116 our $checksum;
    117 our $no_checksum;
    118 our $preserve_paths;
    119 our $compat_libtool;
    120 our $no_compat_libtool;
    121 our $adjust_testname;
    122 our $config;		# Configuration file contents
    123 our $compatibility;	# Compatibility version flag - used to indicate
    124 			# non-standard GCOV data format versions
    125 our @ignore_errors;	# List of errors to ignore (parameter)
    126 our @ignore;		# List of errors to ignore (array)
    127 our $initial;
    128 our $no_recursion = 0;
    129 our $maxdepth;
    130 
    131 our $cwd = `pwd`;
    132 chomp($cwd);
    133 
    134 
    135 #
    136 # Code entry point
    137 #
    138 
    139 # Register handler routine to be called when interrupted
    140 $SIG{"INT"} = \&int_handler;
    141 $SIG{__WARN__} = \&warn_handler;
    142 $SIG{__DIE__} = \&die_handler;
    143 
    144 # Read configuration file if available
    145 if (-r $ENV{"HOME"}."/.lcovrc")
    146 {
    147 	$config = read_config($ENV{"HOME"}."/.lcovrc");
    148 }
    149 elsif (-r "/etc/lcovrc")
    150 {
    151 	$config = read_config("/etc/lcovrc");
    152 }
    153 
    154 if ($config)
    155 {
    156 	# Copy configuration file values to variables
    157 	apply_config({
    158 		"geninfo_gcov_tool"		=> \$gcov_tool,
    159 		"geninfo_adjust_testname"	=> \$adjust_testname,
    160 		"geninfo_checksum"		=> \$checksum,
    161 		"geninfo_no_checksum"		=> \$no_checksum, # deprecated
    162 		"geninfo_compat_libtool"	=> \$compat_libtool});
    163 
    164 	# Merge options
    165 	if (defined($no_checksum))
    166 	{
    167 		$checksum = ($no_checksum ? 0 : 1);
    168 		$no_checksum = undef;
    169 	}
    170 }
    171 
    172 # Parse command line options
    173 if (!GetOptions("test-name=s" => \$test_name,
    174 		"output-filename=s" => \$output_filename,
    175 		"checksum" => \$checksum,
    176 		"no-checksum" => \$no_checksum,
    177 		"base-directory=s" => \$base_directory,
    178 		"version" =>\$version,
    179 		"quiet" => \$quiet,
    180 		"help|?" => \$help,
    181 		"follow" => \$follow,
    182 		"compat-libtool" => \$compat_libtool,
    183 		"no-compat-libtool" => \$no_compat_libtool,
    184 		"gcov-tool=s" => \$gcov_tool,
    185 		"ignore-errors=s" => \@ignore_errors,
    186 		"initial|i" => \$initial,
    187 		"no-recursion" => \$no_recursion,
    188 		))
    189 {
    190 	print(STDERR "Use $tool_name --help to get usage information\n");
    191 	exit(1);
    192 }
    193 else
    194 {
    195 	# Merge options
    196 	if (defined($no_checksum))
    197 	{
    198 		$checksum = ($no_checksum ? 0 : 1);
    199 		$no_checksum = undef;
    200 	}
    201 
    202 	if (defined($no_compat_libtool))
    203 	{
    204 		$compat_libtool = ($no_compat_libtool ? 0 : 1);
    205 		$no_compat_libtool = undef;
    206 	}
    207 }
    208 
    209 @data_directory = @ARGV;
    210 
    211 # Check for help option
    212 if ($help)
    213 {
    214 	print_usage(*STDOUT);
    215 	exit(0);
    216 }
    217 
    218 # Check for version option
    219 if ($version)
    220 {
    221 	print("$tool_name: $lcov_version\n");
    222 	exit(0);
    223 }
    224 
    225 # Make sure test names only contain valid characters
    226 if ($test_name =~ s/\W/_/g)
    227 {
    228 	warn("WARNING: invalid characters removed from testname!\n");
    229 }
    230 
    231 # Adjust test name to include uname output if requested
    232 if ($adjust_testname)
    233 {
    234 	$test_name .= "__".`uname -a`;
    235 	$test_name =~ s/\W/_/g;
    236 }
    237 
    238 # Make sure base_directory contains an absolute path specification
    239 if ($base_directory)
    240 {
    241 	$base_directory = solve_relative_path($cwd, $base_directory);
    242 }
    243 
    244 # Check for follow option
    245 if ($follow)
    246 {
    247 	$follow = "-follow"
    248 }
    249 else
    250 {
    251 	$follow = "";
    252 }
    253 
    254 # Determine checksum mode
    255 if (defined($checksum))
    256 {
    257 	# Normalize to boolean
    258 	$checksum = ($checksum ? 1 : 0);
    259 }
    260 else
    261 {
    262 	# Default is off
    263 	$checksum = 0;
    264 }
    265 
    266 # Determine libtool compatibility mode
    267 if (defined($compat_libtool))
    268 {
    269 	$compat_libtool = ($compat_libtool? 1 : 0);
    270 }
    271 else
    272 {
    273 	# Default is on
    274 	$compat_libtool = 1;
    275 }
    276 
    277 # Determine max depth for recursion
    278 if ($no_recursion)
    279 {
    280 	$maxdepth = "-maxdepth 1";
    281 }
    282 else
    283 {
    284 	$maxdepth = "";
    285 }
    286 
    287 # Check for directory name
    288 if (!@data_directory)
    289 {
    290 	die("No directory specified\n".
    291 	    "Use $tool_name --help to get usage information\n");
    292 }
    293 else
    294 {
    295 	foreach (@data_directory)
    296 	{
    297 		stat($_);
    298 		if (!-r _)
    299 		{
    300 			die("ERROR: cannot read $_!\n");
    301 		}
    302 	}
    303 }
    304 
    305 if (@ignore_errors)
    306 {
    307 	my @expanded;
    308 	my $error;
    309 
    310 	# Expand comma-separated entries
    311 	foreach (@ignore_errors) {
    312 		if (/,/)
    313 		{
    314 			push(@expanded, split(",", $_));
    315 		}
    316 		else
    317 		{
    318 			push(@expanded, $_);
    319 		}
    320 	}
    321 
    322 	foreach (@expanded)
    323 	{
    324 		/^gcov$/ && do { $ignore[$ERROR_GCOV] = 1; next; } ;
    325 		/^source$/ && do { $ignore[$ERROR_SOURCE] = 1; next; };
    326 		die("ERROR: unknown argument for --ignore-errors: $_\n");
    327 	}
    328 }
    329 
    330 if (system_no_output(3, $gcov_tool, "--help") == -1)
    331 {
    332 	die("ERROR: need tool $gcov_tool!\n");
    333 }
    334 
    335 $gcov_version = get_gcov_version();
    336 
    337 if ($gcov_version < $GCOV_VERSION_3_4_0)
    338 {
    339 	if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER)
    340 	{
    341 		$data_file_extension = ".da";
    342 		$graph_file_extension = ".bbg";
    343 	}
    344 	else
    345 	{
    346 		$data_file_extension = ".da";
    347 		$graph_file_extension = ".bb";
    348 	}
    349 }
    350 else
    351 {
    352 	$data_file_extension = ".gcda";
    353 	$graph_file_extension = ".gcno";
    354 }	
    355 
    356 # Check for availability of --preserve-paths option of gcov
    357 if (`$gcov_tool --help` =~ /--preserve-paths/)
    358 {
    359 	$preserve_paths = "--preserve-paths";
    360 }
    361 
    362 # Check output filename
    363 if (defined($output_filename) && ($output_filename ne "-"))
    364 {
    365 	# Initially create output filename, data is appended
    366 	# for each data file processed
    367 	local *DUMMY_HANDLE;
    368 	open(DUMMY_HANDLE, ">$output_filename")
    369 		or die("ERROR: cannot create $output_filename!\n");
    370 	close(DUMMY_HANDLE);
    371 
    372 	# Make $output_filename an absolute path because we're going
    373 	# to change directories while processing files
    374 	if (!($output_filename =~ /^\/(.*)$/))
    375 	{
    376 		$output_filename = $cwd."/".$output_filename;
    377 	}
    378 }
    379 
    380 # Do something
    381 if ($initial)
    382 {
    383 	foreach (@data_directory)
    384 	{
    385 		gen_initial_info($_);
    386 	}
    387 }
    388 else
    389 {
    390 	foreach (@data_directory)
    391 	{
    392 		gen_info($_);
    393 	}
    394 }
    395 info("Finished .info-file creation\n");
    396 
    397 exit(0);
    398 
    399 
    400 
    401 #
    402 # print_usage(handle)
    403 #
    404 # Print usage information.
    405 #
    406 
    407 sub print_usage(*)
    408 {
    409 	local *HANDLE = $_[0];
    410 
    411 	print(HANDLE <<END_OF_USAGE);
    412 Usage: $tool_name [OPTIONS] DIRECTORY
    413 
    414 Traverse DIRECTORY and create a .info file for each data file found. Note
    415 that you may specify more than one directory, all of which are then processed
    416 sequentially.
    417 
    418   -h, --help                        Print this help, then exit
    419   -v, --version                     Print version number, then exit
    420   -q, --quiet                       Do not print progress messages
    421   -i, --initial                     Capture initial zero coverage data
    422   -t, --test-name NAME              Use test case name NAME for resulting data
    423   -o, --output-filename OUTFILE     Write data only to OUTFILE
    424   -f, --follow                      Follow links when searching .da/.gcda files
    425   -b, --base-directory DIR          Use DIR as base directory for relative paths
    426       --(no-)checksum               Enable (disable) line checksumming
    427       --(no-)compat-libtool         Enable (disable) libtool compatibility mode
    428       --gcov-tool TOOL              Specify gcov tool location
    429       --ignore-errors ERROR         Continue after ERROR (gcov, source)
    430       --no-recursion                Exlude subdirectories from processing
    431       --function-coverage           Capture function call counts
    432 
    433 For more information see: $lcov_url
    434 END_OF_USAGE
    435 	;
    436 }
    437 
    438 
    439 #
    440 # gen_info(directory)
    441 #
    442 # Traverse DIRECTORY and create a .info file for each data file found.
    443 # The .info file contains TEST_NAME in the following format:
    444 #
    445 #   TN:<test name>
    446 #
    447 # For each source file name referenced in the data file, there is a section
    448 # containing source code and coverage data:
    449 #
    450 #   SF:<absolute path to the source file>
    451 #   FN:<line number of function start>,<function name> for each function
    452 #   DA:<line number>,<execution count> for each instrumented line
    453 #   LH:<number of lines with an execution count> greater than 0
    454 #   LF:<number of instrumented lines>
    455 #
    456 # Sections are separated by:
    457 #
    458 #   end_of_record
    459 #
    460 # In addition to the main source code file there are sections for each
    461 # #included file containing executable code. Note that the absolute path
    462 # of a source file is generated by interpreting the contents of the respective
    463 # graph file. Relative filenames are prefixed with the directory in which the
    464 # graph file is found. Note also that symbolic links to the graph file will be
    465 # resolved so that the actual file path is used instead of the path to a link.
    466 # This approach is necessary for the mechanism to work with the /proc/gcov
    467 # files.
    468 #
    469 # Die on error.
    470 #
    471 
    472 sub gen_info($)
    473 {
    474 	my $directory = $_[0];
    475 	my @file_list;
    476 
    477 	if (-d $directory)
    478 	{
    479 		info("Scanning $directory for $data_file_extension ".
    480 		     "files ...\n");	
    481 
    482 		@file_list = `find "$directory" $maxdepth $follow -name \\*$data_file_extension -type f 2>/dev/null`;
    483 		chomp(@file_list);
    484 		@file_list or die("ERROR: no $data_file_extension files found ".
    485 				  "in $directory!\n");
    486 		info("Found %d data files in %s\n", $#file_list+1, $directory);
    487 	}
    488 	else
    489 	{
    490 		@file_list = ($directory);
    491 	}
    492 
    493 	# Process all files in list
    494 	foreach (@file_list) { process_dafile($_); }
    495 }
    496 
    497 
    498 #
    499 # process_dafile(da_filename)
    500 #
    501 # Create a .info file for a single data file.
    502 #
    503 # Die on error.
    504 #
    505 
    506 sub process_dafile($)
    507 {
    508 	info("Processing %s\n", $_[0]);
    509 
    510 	my $da_filename;	# Name of data file to process
    511 	my $da_dir;		# Directory of data file
    512 	my $source_dir;		# Directory of source file
    513 	my $da_basename;	# data filename without ".da/.gcda" extension
    514 	my $bb_filename;	# Name of respective graph file
    515 	my %bb_content;		# Contents of graph file
    516 	my $gcov_error;		# Error code of gcov tool
    517 	my $object_dir;		# Directory containing all object files
    518 	my $source_filename;	# Name of a source code file
    519 	my $gcov_file;		# Name of a .gcov file
    520 	my @gcov_content;	# Content of a .gcov file
    521 	my @gcov_branches;	# Branch content of a .gcov file
    522 	my @gcov_functions;	# Function calls of a .gcov file
    523 	my @gcov_list;		# List of generated .gcov files
    524 	my $line_number;	# Line number count
    525 	my $lines_hit;		# Number of instrumented lines hit
    526 	my $lines_found;	# Number of instrumented lines found
    527 	my $funcs_hit;		# Number of instrumented functions hit
    528 	my $funcs_found;	# Number of instrumented functions found
    529 	my $source;		# gcov source header information
    530 	my $object;		# gcov object header information
    531 	my @matches;		# List of absolute paths matching filename
    532 	my @unprocessed;	# List of unprocessed source code files
    533 	my $base_dir;		# Base directory for current file
    534 	my @result;
    535 	my $index;
    536 	my $da_renamed;		# If data file is to be renamed
    537 	local *INFO_HANDLE;
    538 
    539 	# Get path to data file in absolute and normalized form (begins with /,
    540 	# contains no more ../ or ./)
    541 	$da_filename = solve_relative_path($cwd, $_[0]);
    542 
    543 	# Get directory and basename of data file
    544 	($da_dir, $da_basename) = split_filename($da_filename);
    545 
    546 	# avoid files from .libs dirs 	 
    547 	if ($compat_libtool && $da_dir =~ m/(.*)\/\.libs$/) {
    548 		$source_dir = $1;
    549 	} else {
    550 		$source_dir = $da_dir;
    551 	}
    552 
    553 	if (-z $da_filename)
    554 	{
    555 		$da_renamed = 1;
    556 	}
    557 	else
    558 	{
    559 		$da_renamed = 0;
    560 	}
    561 
    562 	# Construct base_dir for current file
    563 	if ($base_directory)
    564 	{
    565 		$base_dir = $base_directory;
    566 	}
    567 	else
    568 	{
    569 		$base_dir = $source_dir;
    570 	}
    571 
    572 	# Check for writable $base_dir (gcov will try to write files there)
    573 	stat($base_dir);
    574 	if (!-w _)
    575 	{
    576 		die("ERROR: cannot write to directory $base_dir!\n");
    577 	}
    578 
    579 	# Construct name of graph file
    580 	$bb_filename = $da_dir."/".$da_basename.$graph_file_extension;
    581 
    582 	# Find out the real location of graph file in case we're just looking at
    583 	# a link
    584 	while (readlink($bb_filename))
    585 	{
    586 		my $last_dir = dirname($bb_filename);
    587 
    588 		$bb_filename = readlink($bb_filename);
    589 		$bb_filename = solve_relative_path($last_dir, $bb_filename);
    590 	}
    591 
    592 	# Ignore empty graph file (e.g. source file with no statement)
    593 	if (-z $bb_filename)
    594 	{
    595 		warn("WARNING: empty $bb_filename (skipped)\n");
    596 		return;
    597 	}
    598 
    599 	# Read contents of graph file into hash. We need it later to find out
    600 	# the absolute path to each .gcov file created as well as for
    601 	# information about functions and their source code positions.
    602 	if ($gcov_version < $GCOV_VERSION_3_4_0)
    603 	{
    604 		if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER)
    605 		{
    606 			%bb_content = read_hammer_bbg_file($bb_filename,
    607 							   $base_dir);
    608 		}
    609 		else
    610 		{
    611 			%bb_content = read_bb_file($bb_filename, $base_dir);
    612 		}
    613 	} 
    614 	else
    615 	{
    616 		%bb_content = read_gcno_file($bb_filename, $base_dir);
    617 	} 
    618 
    619 	# Set $object_dir to real location of object files. This may differ
    620 	# from $da_dir if the graph file is just a link to the "real" object
    621 	# file location.
    622 	$object_dir = dirname($bb_filename);
    623 
    624 	# Is the data file in a different directory? (this happens e.g. with
    625 	# the gcov-kernel patch)
    626 	if ($object_dir ne $da_dir)
    627 	{
    628 		# Need to create link to data file in $object_dir
    629 		system("ln", "-s", $da_filename, 
    630 		       "$object_dir/$da_basename$data_file_extension")
    631 			and die ("ERROR: cannot create link $object_dir/".
    632 				 "$da_basename$data_file_extension!\n");
    633 	}
    634 
    635 	# Change to directory containing data files and apply GCOV
    636         chdir($base_dir);
    637 
    638 	if ($da_renamed)
    639 	{
    640 		# Need to rename empty data file to workaround
    641 	        # gcov <= 3.2.x bug (Abort)
    642 		system_no_output(3, "mv", "$da_filename", "$da_filename.ori")
    643 			and die ("ERROR: cannot rename $da_filename\n");
    644 	}
    645 
    646 	# Execute gcov command and suppress standard output
    647 	if ($preserve_paths)
    648 	{
    649 		$gcov_error = system_no_output(1, $gcov_tool, $da_filename,
    650 					       "-o", $object_dir,
    651 					       "--preserve-paths",
    652 					       "-b");
    653 	}
    654 	else
    655 	{
    656 		$gcov_error = system_no_output(1, $gcov_tool, $da_filename,
    657 					       "-o", $object_dir,
    658 					       "-b");
    659 	}
    660 
    661 	if ($da_renamed)
    662 	{
    663 		system_no_output(3, "mv", "$da_filename.ori", "$da_filename")
    664 			and die ("ERROR: cannot rename $da_filename.ori");
    665 	}
    666 
    667 	# Clean up link
    668 	if ($object_dir ne $da_dir)
    669 	{
    670 		unlink($object_dir."/".$da_basename.$data_file_extension);
    671 	}
    672 
    673 	if ($gcov_error)
    674 	{
    675 		if ($ignore[$ERROR_GCOV])
    676 		{
    677 			warn("WARNING: GCOV failed for $da_filename!\n");
    678 			return;
    679 		}
    680 		die("ERROR: GCOV failed for $da_filename!\n");
    681 	}
    682 
    683 	# Collect data from resulting .gcov files and create .info file
    684 	@gcov_list = glob("*.gcov");
    685 
    686 	# Check for files
    687 	if (!@gcov_list)
    688 	{
    689 		warn("WARNING: gcov did not create any files for ".
    690 		     "$da_filename!\n");
    691 	}
    692 
    693 	# Check whether we're writing to a single file
    694 	if ($output_filename)
    695 	{
    696 		if ($output_filename eq "-")
    697 		{
    698 			*INFO_HANDLE = *STDOUT;
    699 		}
    700 		else
    701 		{
    702 			# Append to output file
    703 			open(INFO_HANDLE, ">>$output_filename")
    704 				or die("ERROR: cannot write to ".
    705 				       "$output_filename!\n");
    706 		}
    707 	}
    708 	else
    709 	{
    710 		# Open .info file for output
    711 		open(INFO_HANDLE, ">$da_filename.info")
    712 			or die("ERROR: cannot create $da_filename.info!\n");
    713 	}
    714 
    715 	# Write test name
    716 	printf(INFO_HANDLE "TN:%s\n", $test_name);
    717 
    718 	# Traverse the list of generated .gcov files and combine them into a
    719 	# single .info file
    720 	@unprocessed = keys(%bb_content);
    721 	foreach $gcov_file (@gcov_list)
    722 	{
    723 		($source, $object) = read_gcov_header($gcov_file);
    724 
    725 		if (defined($source))
    726 		{
    727 			$source = solve_relative_path($base_dir, $source);
    728 		}
    729 
    730 		# gcov will happily create output even if there's no source code
    731 		# available - this interferes with checksum creation so we need
    732 		# to pull the emergency brake here.
    733 		if (defined($source) && ! -r $source && $checksum)
    734 		{
    735 			if ($ignore[$ERROR_SOURCE])
    736 			{
    737 				warn("WARNING: could not read source file ".
    738 				     "$source\n");
    739 				next;
    740 			}
    741 			die("ERROR: could not read source file $source\n");
    742 		}
    743 
    744 		@matches = match_filename(defined($source) ? $source :
    745 					  $gcov_file, keys(%bb_content));
    746 
    747 		# Skip files that are not mentioned in the graph file
    748 		if (!@matches)
    749 		{
    750 			warn("WARNING: cannot find an entry for ".$gcov_file.
    751 			     " in $graph_file_extension file, skipping ".
    752 			     "file!\n");
    753 			unlink($gcov_file);
    754 			next;
    755 		}
    756 
    757 		# Read in contents of gcov file
    758 		@result = read_gcov_file($gcov_file);
    759 		@gcov_content = @{$result[0]};
    760 		@gcov_branches = @{$result[1]};
    761 		@gcov_functions = @{$result[2]};
    762 
    763 		# Skip empty files
    764 		if (!@gcov_content)
    765 		{
    766 			warn("WARNING: skipping empty file ".$gcov_file."\n");
    767 			unlink($gcov_file);
    768 			next;
    769 		}
    770 
    771 		if (scalar(@matches) == 1)
    772 		{
    773 			# Just one match
    774 			$source_filename = $matches[0];
    775 		}
    776 		else
    777 		{
    778 			# Try to solve the ambiguity
    779 			$source_filename = solve_ambiguous_match($gcov_file,
    780 						\@matches, \@gcov_content);
    781 		}
    782 
    783 		# Remove processed file from list
    784 		for ($index = scalar(@unprocessed) - 1; $index >= 0; $index--)
    785 		{
    786 			if ($unprocessed[$index] eq $source_filename)
    787 			{
    788 				splice(@unprocessed, $index, 1);
    789 				last;
    790 			}
    791 		}
    792 
    793 		# Write absolute path of source file
    794 		printf(INFO_HANDLE "SF:%s\n", $source_filename);
    795 
    796 		# Write function-related information
    797 		if (defined($bb_content{$source_filename}))
    798 		{
    799 			foreach (split(",",$bb_content{$source_filename}))
    800 			{
    801 				my ($fn, $line) = split("=", $_);
    802 
    803 				if ($fn eq "") {
    804 					next;
    805 				}
    806 
    807 				# Normalize function name
    808 				$fn =~ s/\W/_/g;
    809 
    810 				print(INFO_HANDLE "FN:$line,$fn\n");
    811 			}
    812 		}
    813 
    814 		#--
    815 		#-- FNDA: <call-count>, <function-name>
    816 		#-- FNF: overall count of functions
    817 		#-- FNH: overall count of functions with non-zero call count
    818 		#--
    819 		$funcs_found = 0;
    820 		$funcs_hit = 0;
    821 		while (@gcov_functions)
    822 		{
    823 			printf(INFO_HANDLE "FNDA:%s,%s\n",
    824 				       $gcov_functions[0],
    825 				       $gcov_functions[1]);
    826 				$funcs_found++;
    827 			$funcs_hit++ if $gcov_functions[0];
    828 			splice(@gcov_functions,0,2);
    829 		}
    830 		if ($funcs_found > 0) {
    831 			printf(INFO_HANDLE "FNF:%s\n", $funcs_found);
    832 			printf(INFO_HANDLE "FNH:%s\n", $funcs_hit);
    833 		}
    834 
    835 		# Reset line counters
    836 		$line_number = 0;
    837 		$lines_found = 0;
    838 		$lines_hit = 0;
    839 
    840 		# Write coverage information for each instrumented line
    841 		# Note: @gcov_content contains a list of (flag, count, source)
    842 		# tuple for each source code line
    843 		while (@gcov_content)
    844 		{
    845 			$line_number++;
    846 
    847 			# Check for instrumented line
    848 			if ($gcov_content[0])
    849 			{
    850 				$lines_found++;
    851 				printf(INFO_HANDLE "DA:".$line_number.",".
    852 				       $gcov_content[1].($checksum ?
    853 				       ",". md5_base64($gcov_content[2]) : "").
    854 				       "\n");
    855 
    856 				# Increase $lines_hit in case of an execution
    857 				# count>0
    858 				if ($gcov_content[1] > 0) { $lines_hit++; }
    859 			}
    860 
    861 			# Remove already processed data from array
    862 			splice(@gcov_content,0,3);
    863 		}
    864 
    865 		#--
    866 		#-- BA: <code-line>, <branch-coverage>
    867 		#--
    868 		#-- print one BA line for every branch of a
    869 		#-- conditional.  <branch-coverage> values
    870 		#-- are:
    871 		#--     0 - not executed
    872 		#--     1 - executed but not taken
    873 		#--     2 - executed and taken
    874 		#--
    875 		while (@gcov_branches)
    876 		{
    877 			if ($gcov_branches[0])
    878 			{
    879 				printf(INFO_HANDLE "BA:%s,%s\n",
    880 				       $gcov_branches[0],
    881 				       $gcov_branches[1]);
    882 			}
    883 			splice(@gcov_branches,0,2);
    884 		}
    885 
    886 		# Write line statistics and section separator
    887 		printf(INFO_HANDLE "LF:%s\n", $lines_found);
    888 		printf(INFO_HANDLE "LH:%s\n", $lines_hit);
    889 		print(INFO_HANDLE "end_of_record\n");
    890 
    891 		# Remove .gcov file after processing
    892 		unlink($gcov_file);
    893 	}
    894 
    895 	# Check for files which show up in the graph file but were never
    896 	# processed
    897 	if (@unprocessed && @gcov_list)
    898 	{
    899 		foreach (@unprocessed)
    900 		{
    901 			warn("WARNING: no data found for $_\n");
    902 		}
    903 	}
    904 
    905 	if (!($output_filename && ($output_filename eq "-")))
    906 	{
    907 		close(INFO_HANDLE);
    908 	}
    909 
    910 	# Change back to initial directory
    911 	chdir($cwd);
    912 }
    913 
    914 
    915 #
    916 # solve_relative_path(path, dir)
    917 #
    918 # Solve relative path components of DIR which, if not absolute, resides in PATH.
    919 #
    920 
    921 sub solve_relative_path($$)
    922 {
    923 	my $path = $_[0];
    924 	my $dir = $_[1];
    925 	my $result;
    926 
    927 	$result = $dir;
    928 	# Prepend path if not absolute
    929 	if ($dir =~ /^[^\/]/)
    930 	{
    931 		$result = "$path/$result";
    932 	}
    933 
    934 	# Remove //
    935 	$result =~ s/\/\//\//g;
    936 
    937 	# Remove .
    938 	$result =~ s/\/\.\//\//g;
    939 
    940 	# Solve ..
    941 	while ($result =~ s/\/[^\/]+\/\.\.\//\//)
    942 	{
    943 	}
    944 
    945 	# Remove preceding ..
    946 	$result =~ s/^\/\.\.\//\//g;
    947 
    948 	return $result;
    949 }
    950 
    951 
    952 #
    953 # match_filename(gcov_filename, list)
    954 #
    955 # Return a list of those entries of LIST which match the relative filename
    956 # GCOV_FILENAME.
    957 #
    958 
    959 sub match_filename($@)
    960 {
    961 	my $filename = shift;
    962 	my @list = @_;
    963 	my @result;
    964 
    965 	$filename =~ s/^(.*).gcov$/$1/;
    966 
    967 	if ($filename =~ /^\/(.*)$/)
    968 	{
    969 		$filename = "$1";
    970 	}
    971 
    972 	foreach (@list)
    973 	{
    974 		if (/\/\Q$filename\E(.*)$/ && $1 eq "")
    975 		{
    976 			@result = (@result, $_);
    977 		}
    978 	}
    979 	return @result;
    980 }
    981 
    982 
    983 #
    984 # solve_ambiguous_match(rel_filename, matches_ref, gcov_content_ref)
    985 #
    986 # Try to solve ambiguous matches of mapping (gcov file) -> (source code) file
    987 # by comparing source code provided in the GCOV file with that of the files
    988 # in MATCHES. REL_FILENAME identifies the relative filename of the gcov
    989 # file.
    990 # 
    991 # Return the one real match or die if there is none.
    992 #
    993 
    994 sub solve_ambiguous_match($$$)
    995 {
    996 	my $rel_name = $_[0];
    997 	my $matches = $_[1];
    998 	my $content = $_[2];
    999 	my $filename;
   1000 	my $index;
   1001 	my $no_match;
   1002 	local *SOURCE;
   1003 
   1004 	# Check the list of matches
   1005 	foreach $filename (@$matches)
   1006 	{
   1007 
   1008 		# Compare file contents
   1009 		open(SOURCE, $filename)
   1010 			or die("ERROR: cannot read $filename!\n");
   1011 
   1012 		$no_match = 0;
   1013 		for ($index = 2; <SOURCE>; $index += 3)
   1014 		{
   1015 			chomp;
   1016 
   1017 			if ($_ ne @$content[$index])
   1018 			{
   1019 				$no_match = 1;
   1020 				last;
   1021 			}
   1022 		}
   1023 
   1024 		close(SOURCE);
   1025 
   1026 		if (!$no_match)
   1027 		{
   1028 			info("Solved source file ambiguity for $rel_name\n");
   1029 			return $filename;
   1030 		}
   1031 	}
   1032 
   1033 	die("ERROR: could not match gcov data for $rel_name!\n");
   1034 }
   1035 
   1036 
   1037 #
   1038 # split_filename(filename)
   1039 #
   1040 # Return (path, filename, extension) for a given FILENAME.
   1041 #
   1042 
   1043 sub split_filename($)
   1044 {
   1045 	my @path_components = split('/', $_[0]);
   1046 	my @file_components = split('\.', pop(@path_components));
   1047 	my $extension = pop(@file_components);
   1048 
   1049 	return (join("/",@path_components), join(".",@file_components),
   1050 		$extension);
   1051 }
   1052 
   1053 
   1054 #
   1055 # get_dir(filename);
   1056 #
   1057 # Return the directory component of a given FILENAME.
   1058 #
   1059 
   1060 sub get_dir($)
   1061 {
   1062 	my @components = split("/", $_[0]);
   1063 	pop(@components);
   1064 
   1065 	return join("/", @components);
   1066 }
   1067 
   1068 
   1069 #
   1070 # read_gcov_header(gcov_filename)
   1071 #
   1072 # Parse file GCOV_FILENAME and return a list containing the following
   1073 # information:
   1074 #
   1075 #   (source, object)
   1076 #
   1077 # where:
   1078 #
   1079 # source: complete relative path of the source code file (gcc >= 3.3 only)
   1080 # object: name of associated graph file
   1081 #
   1082 # Die on error.
   1083 #
   1084 
   1085 sub read_gcov_header($)
   1086 {
   1087 	my $source;
   1088 	my $object;
   1089 	local *INPUT;
   1090 
   1091 	if (!open(INPUT, $_[0]))
   1092 	{
   1093 		if ($ignore_errors[$ERROR_GCOV])
   1094 		{
   1095 			warn("WARNING: cannot read $_[0]!\n");
   1096 			return (undef,undef);
   1097 		}
   1098 		die("ERROR: cannot read $_[0]!\n");
   1099 	}
   1100 
   1101 	while (<INPUT>)
   1102 	{
   1103 		chomp($_);
   1104 
   1105 		if (/^\s+-:\s+0:Source:(.*)$/)
   1106 		{
   1107 			# Source: header entry
   1108 			$source = $1;
   1109 		}
   1110 		elsif (/^\s+-:\s+0:Object:(.*)$/)
   1111 		{
   1112 			# Object: header entry
   1113 			$object = $1;
   1114 		}
   1115 		else
   1116 		{
   1117 			last;
   1118 		}
   1119 	}
   1120 
   1121 	close(INPUT);
   1122 
   1123 	return ($source, $object);
   1124 }
   1125 
   1126 
   1127 #
   1128 # read_gcov_file(gcov_filename)
   1129 #
   1130 # Parse file GCOV_FILENAME (.gcov file format) and return the list:
   1131 # (reference to gcov_content, reference to gcov_branch, reference to gcov_func)
   1132 #
   1133 # gcov_content is a list of 3 elements
   1134 # (flag, count, source) for each source code line:
   1135 #
   1136 # $result[($line_number-1)*3+0] = instrumentation flag for line $line_number
   1137 # $result[($line_number-1)*3+1] = execution count for line $line_number
   1138 # $result[($line_number-1)*3+2] = source code text for line $line_number
   1139 #
   1140 # gcov_branch is a list of 2 elements
   1141 # (linenumber, branch result) for each branch
   1142 #
   1143 # gcov_func is a list of 2 elements
   1144 # (number of calls, function name) for each function
   1145 #
   1146 # Die on error.
   1147 #
   1148 
   1149 sub read_gcov_file($)
   1150 {
   1151 	my $filename = $_[0];
   1152 	my @result = ();
   1153 	my @branches = ();
   1154 	my @functions = ();
   1155 	my $number;
   1156 	local *INPUT;
   1157 
   1158 	open(INPUT, $filename)
   1159 		or die("ERROR: cannot read $filename!\n");
   1160 
   1161 	if ($gcov_version < $GCOV_VERSION_3_3_0)
   1162 	{
   1163 		# Expect gcov format as used in gcc < 3.3
   1164 		while (<INPUT>)
   1165 		{
   1166 			chomp($_);
   1167 
   1168 			if (/^\t\t(.*)$/)
   1169 			{
   1170 				# Uninstrumented line
   1171 				push(@result, 0);
   1172 				push(@result, 0);
   1173 				push(@result, $1);
   1174 			}
   1175 			elsif (/^branch/)
   1176 			{
   1177 				# Branch execution data
   1178 				push(@branches, scalar(@result) / 3);
   1179 				if (/^branch \d+ never executed$/)
   1180 				{
   1181 					push(@branches, 0);
   1182 				}
   1183 				elsif (/^branch \d+ taken = 0%/)
   1184 				{
   1185 					push(@branches, 1);
   1186 				}
   1187 				else
   1188 				{
   1189 					push(@branches, 2);
   1190 				}
   1191 			}
   1192 			elsif (/^call/ || /^function/)
   1193 			{
   1194 				# Function call return data
   1195 			}
   1196 			else
   1197 			{
   1198 				# Source code execution data
   1199 				$number = (split(" ",substr($_, 0, 16)))[0];
   1200 
   1201 				# Check for zero count which is indicated
   1202 				# by ######
   1203 				if ($number eq "######") { $number = 0;	}
   1204 
   1205 				push(@result, 1);
   1206 				push(@result, $number);
   1207 				push(@result, substr($_, 16));
   1208 			}
   1209 		}
   1210 	}
   1211 	else
   1212 	{
   1213 		# Expect gcov format as used in gcc >= 3.3
   1214 		while (<INPUT>)
   1215 		{
   1216 			chomp($_);
   1217 
   1218 			if (/^branch\s+\d+\s+(\S+)\s+(\S+)/)
   1219 			{
   1220 				# Branch execution data
   1221 				push(@branches, scalar(@result) / 3);
   1222 				if ($1 eq "never")
   1223 				{
   1224 					push(@branches, 0);
   1225 				}
   1226 				elsif ($2 eq "0%")
   1227 				{
   1228 					push(@branches, 1);
   1229 				}
   1230 				else
   1231 				{
   1232 					push(@branches, 2);
   1233 				}
   1234 			}
   1235 			elsif (/^function\s+(\S+)\s+called\s+(\d+)/)
   1236 			{
   1237 				push(@functions, $2, $1);
   1238 			}
   1239 			elsif (/^call/)
   1240 			{
   1241 				# Function call return data
   1242 			}
   1243 			elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/)
   1244 			{
   1245 				# <exec count>:<line number>:<source code>
   1246 				if ($2 eq "0")
   1247 				{
   1248 					# Extra data
   1249 				}
   1250 				elsif ($1 eq "-")
   1251 				{
   1252 					# Uninstrumented line
   1253 					push(@result, 0);
   1254 					push(@result, 0);
   1255 					push(@result, $3);
   1256 				}
   1257 				else
   1258 				{
   1259 					# Source code execution data
   1260 					$number = $1;
   1261 
   1262 					# Check for zero count
   1263 					if ($number eq "#####")	{ $number = 0; }
   1264 
   1265 					push(@result, 1);
   1266 					push(@result, $number);
   1267 					push(@result, $3);
   1268 				}
   1269 			}
   1270 		}
   1271 	}
   1272 
   1273 	close(INPUT);
   1274 	return(\@result, \@branches, \@functions);
   1275 }
   1276 
   1277 
   1278 #
   1279 # read_bb_file(bb_filename, base_dir)
   1280 #
   1281 # Read .bb file BB_FILENAME and return a hash containing the following
   1282 # mapping:
   1283 #
   1284 #   filename -> comma-separated list of pairs (function name=starting
   1285 #               line number) to indicate the starting line of a function or
   1286 #               =name to indicate an instrumented line
   1287 #
   1288 # for each entry in the .bb file. Filenames are absolute, i.e. relative
   1289 # filenames are prefixed with BASE_DIR.
   1290 #
   1291 # Die on error.
   1292 #
   1293 
   1294 sub read_bb_file($$)
   1295 {
   1296 	my $bb_filename		= $_[0];
   1297 	my $base_dir = $_[1];
   1298 	my %result;
   1299 	my $filename;
   1300 	my $function_name;
   1301 	my $minus_one		= sprintf("%d", 0x80000001);
   1302 	my $minus_two		= sprintf("%d", 0x80000002);
   1303 	my $value;
   1304 	my $packed_word;
   1305 	local *INPUT;
   1306 
   1307 	open(INPUT, $bb_filename)
   1308 		or die("ERROR: cannot read $bb_filename!\n");
   1309 
   1310 	binmode(INPUT);
   1311 
   1312 	# Read data in words of 4 bytes
   1313 	while (read(INPUT, $packed_word, 4) == 4)
   1314 	{
   1315 		# Decode integer in intel byteorder
   1316 		$value = unpack_int32($packed_word, 0);
   1317 
   1318 		# Note: the .bb file format is documented in GCC info pages
   1319 		if ($value == $minus_one)
   1320 		{
   1321 			# Filename follows
   1322 			$filename = read_string(*INPUT, $minus_one)
   1323 				or die("ERROR: incomplete filename in ".
   1324 				       "$bb_filename!\n");
   1325 
   1326 			# Make path absolute
   1327 			$filename = solve_relative_path($base_dir, $filename);
   1328 
   1329 			# Insert into hash if not yet present.
   1330 			# This is necessary because functions declared as
   1331 			# "inline" are not listed as actual functions in
   1332 			# .bb files
   1333 			if (!$result{$filename})
   1334 			{
   1335 				$result{$filename}="";
   1336 			}
   1337 		}
   1338 		elsif ($value == $minus_two)
   1339 		{
   1340 			# Function name follows
   1341 			$function_name = read_string(*INPUT, $minus_two)
   1342 				 or die("ERROR: incomplete function ".
   1343 					"name in $bb_filename!\n");
   1344 			$function_name =~ s/\W/_/g;
   1345 		}
   1346 		elsif ($value > 0)
   1347 		{
   1348 			if (defined($filename))
   1349 			{
   1350 				$result{$filename} .=
   1351 					($result{$filename} ? "," : "").
   1352 					"=$value";
   1353 			}
   1354 			else
   1355 			{
   1356 				warn("WARNING: unassigned line".
   1357 				     " number in .bb file ".
   1358 				     "$bb_filename\n");
   1359 			}
   1360 			if ($function_name)
   1361 			{
   1362 				# Got a full entry filename, funcname, lineno
   1363 				# Add to resulting hash
   1364 
   1365 				$result{$filename}.=
   1366 				  ($result{$filename} ? "," : "").
   1367 				  join("=",($function_name,$value));
   1368 				undef($function_name);
   1369 			}
   1370 		}
   1371 	}
   1372 	close(INPUT);
   1373 
   1374 	if (!scalar(keys(%result)))
   1375 	{
   1376 		die("ERROR: no data found in $bb_filename!\n");
   1377 	}
   1378 	return %result;
   1379 }
   1380 
   1381 
   1382 #
   1383 # read_string(handle, delimiter);
   1384 #
   1385 # Read and return a string in 4-byte chunks from HANDLE until DELIMITER
   1386 # is found.
   1387 #
   1388 # Return empty string on error.
   1389 #
   1390 
   1391 sub read_string(*$)
   1392 {
   1393 	my $HANDLE	= $_[0];
   1394 	my $delimiter	= $_[1];
   1395 	my $string	= "";
   1396 	my $packed_word;
   1397 	my $value;
   1398 
   1399 	while (read($HANDLE,$packed_word,4) == 4)
   1400 	{
   1401 		$value = unpack_int32($packed_word, 0);
   1402 
   1403 		if ($value == $delimiter)
   1404 		{
   1405 			# Remove trailing nil bytes
   1406 			$/="\0";
   1407 			while (chomp($string)) {};
   1408 			$/="\n";
   1409 			return($string);
   1410 		}
   1411 
   1412 		$string = $string.$packed_word;
   1413 	}
   1414 	return("");
   1415 }
   1416 
   1417 
   1418 #
   1419 # read_gcno_file(bb_filename, base_dir)
   1420 #
   1421 # Read .gcno file BB_FILENAME and return a hash containing the following
   1422 # mapping:
   1423 #
   1424 #   filename -> comma-separated list of pairs (function name=starting
   1425 #               line number) to indicate the starting line of a function or
   1426 #               =name to indicate an instrumented line
   1427 #
   1428 # for each entry in the .gcno file. Filenames are absolute, i.e. relative
   1429 # filenames are prefixed with BASE_DIR.
   1430 #
   1431 # Die on error.
   1432 #
   1433 
   1434 sub read_gcno_file($$)
   1435 {
   1436 	my $gcno_filename	= $_[0];
   1437 	my $base_dir = $_[1];
   1438 	my %result;
   1439 	my $filename;
   1440 	my $function_name;
   1441 	my $lineno;
   1442 	my $length;
   1443 	my $value;
   1444 	my $endianness;
   1445 	my $blocks;
   1446 	my $packed_word;
   1447 	my $string;
   1448 	local *INPUT;
   1449 
   1450 	open(INPUT, $gcno_filename)
   1451 		or die("ERROR: cannot read $gcno_filename!\n");
   1452 
   1453 	binmode(INPUT);
   1454 	
   1455 	read(INPUT, $packed_word, 4) == 4
   1456 		or die("ERROR: Invalid gcno file format\n");
   1457 
   1458 	$value = unpack_int32($packed_word, 0);
   1459 	$endianness = !($value == $GCNO_FILE_MAGIC);
   1460 
   1461 	unpack_int32($packed_word, $endianness) == $GCNO_FILE_MAGIC
   1462 		or die("ERROR: gcno file magic does not match\n");
   1463 
   1464 	seek(INPUT, 8, 1);
   1465 
   1466 	# Read data in words of 4 bytes
   1467 	while (read(INPUT, $packed_word, 4) == 4)
   1468 	{
   1469 		# Decode integer in intel byteorder
   1470 		$value = unpack_int32($packed_word, $endianness);
   1471 
   1472 		if ($value == $GCNO_FUNCTION_TAG)
   1473 		{
   1474 			# skip length, ident and checksum
   1475 			seek(INPUT, 12, 1);
   1476 			(undef, $function_name) =
   1477 				read_gcno_string(*INPUT, $endianness);
   1478 			$function_name =~ s/\W/_/g;
   1479 			(undef, $filename) =
   1480 				read_gcno_string(*INPUT, $endianness);
   1481 			$filename = solve_relative_path($base_dir, $filename);
   1482 
   1483 			read(INPUT, $packed_word, 4);
   1484 			$lineno = unpack_int32($packed_word, $endianness);
   1485 
   1486 			$result{$filename}.=
   1487 			    ($result{$filename} ? "," : "").
   1488 				join("=",($function_name,$lineno));
   1489 		}
   1490 		elsif ($value == $GCNO_LINES_TAG)
   1491 		{
   1492 			# Check for names of files containing inlined code
   1493 			# included in this file
   1494 			read(INPUT, $packed_word, 4);
   1495 			$length = unpack_int32($packed_word, $endianness);
   1496 			if ($length > 0)
   1497 			{
   1498 				# Block number
   1499 				read(INPUT, $packed_word, 4);
   1500 				$length--;
   1501 			}
   1502 			while ($length > 0)
   1503 			{
   1504 				read(INPUT, $packed_word, 4);
   1505 				$lineno = unpack_int32($packed_word,
   1506 						       $endianness);
   1507 				$length--;
   1508 				if ($lineno != 0)
   1509 				{
   1510 					if (defined($filename))
   1511 					{
   1512 						$result{$filename} .=
   1513 							($result{$filename} ? "," : "").
   1514 							"=$lineno";
   1515 					}
   1516 					else
   1517 					{
   1518 						warn("WARNING: unassigned line".
   1519 						     " number in .gcno file ".
   1520 						     "$gcno_filename\n");
   1521 					}
   1522 					next;
   1523 				}
   1524 				last if ($length == 0);
   1525 				($blocks, $string) =
   1526 					read_gcno_string(*INPUT, $endianness);
   1527 				if (defined($string))
   1528 				{
   1529 					$filename = $string;
   1530 				}
   1531 				if ($blocks > 1)
   1532 				{
   1533 					$filename = solve_relative_path(
   1534 							$base_dir, $filename);
   1535 					if (!defined($result{$filename}))
   1536 					{
   1537 						$result{$filename} = "";
   1538 					}
   1539 				}
   1540 				$length -= $blocks;
   1541 			}
   1542 		}
   1543 		else
   1544 		{
   1545 			read(INPUT, $packed_word, 4);
   1546 			$length = unpack_int32($packed_word, $endianness);
   1547 			seek(INPUT, 4 * $length, 1);
   1548 		}
   1549 	}
   1550 	close(INPUT);
   1551 
   1552 	if (!scalar(keys(%result)))
   1553 	{
   1554 		die("ERROR: no data found in $gcno_filename!\n");
   1555 	}
   1556 	return %result;
   1557 }
   1558 
   1559 
   1560 #
   1561 # read_gcno_string(handle, endianness);
   1562 #
   1563 # Read a string in 4-byte chunks from HANDLE.
   1564 #
   1565 # Return (number of 4-byte chunks read, string).
   1566 #
   1567 
   1568 sub read_gcno_string(*$)
   1569 {
   1570 	my $handle		= $_[0];
   1571 	my $endianness		= $_[1];
   1572 	my $number_of_blocks	= 0;
   1573 	my $string		= "";
   1574 	my $packed_word;
   1575 
   1576 	read($handle, $packed_word, 4) == 4
   1577 		or die("ERROR: reading string\n");
   1578 
   1579 	$number_of_blocks = unpack_int32($packed_word, $endianness);
   1580 
   1581 	if ($number_of_blocks == 0)
   1582 	{
   1583 		return (1, undef);
   1584 	}
   1585 
   1586 	if (read($handle, $packed_word, 4 * $number_of_blocks) !=
   1587 	     4 * $number_of_blocks)
   1588 	{
   1589 		my $msg = "invalid string size ".(4 * $number_of_blocks)." in ".
   1590 			  "gcno file at position ".tell($handle)."\n";
   1591 		if ($ignore[$ERROR_SOURCE])
   1592 		{
   1593 			warn("WARNING: $msg");
   1594 			return (1, undef);
   1595 		}
   1596 		else
   1597 		{
   1598 			die("ERROR: $msg");
   1599 		}
   1600 	}
   1601 
   1602 	$string = $string . $packed_word;
   1603 
   1604 	# Remove trailing nil bytes
   1605 	$/="\0";
   1606 	while (chomp($string)) {};
   1607 	$/="\n";
   1608 
   1609 	return(1 + $number_of_blocks, $string);
   1610 }
   1611 
   1612 
   1613 #
   1614 # read_hammer_bbg_file(bb_filename, base_dir)
   1615 #
   1616 # Read .bbg file BB_FILENAME and return a hash containing the following
   1617 # mapping:
   1618 #
   1619 #   filename -> comma-separated list of pairs (function name=starting
   1620 #               line number) to indicate the starting line of a function or
   1621 #               =name to indicate an instrumented line
   1622 #
   1623 # for each entry in the .bbg file. Filenames are absolute, i.e. relative
   1624 # filenames are prefixed with BASE_DIR.
   1625 #
   1626 # Die on error.
   1627 #
   1628 
   1629 sub read_hammer_bbg_file($$)
   1630 {
   1631 	my $bbg_filename = $_[0];
   1632 	my $base_dir = $_[1];
   1633 	my %result;
   1634 	my $filename;
   1635 	my $function_name;
   1636 	my $first_line;
   1637 	my $lineno;
   1638 	my $length;
   1639 	my $value;
   1640 	my $endianness;
   1641 	my $blocks;
   1642 	my $packed_word;
   1643 	local *INPUT;
   1644 
   1645 	open(INPUT, $bbg_filename)
   1646 		or die("ERROR: cannot read $bbg_filename!\n");
   1647 
   1648 	binmode(INPUT);
   1649 	
   1650 	# Read magic
   1651 	read(INPUT, $packed_word, 4) == 4
   1652 		or die("ERROR: invalid bbg file format\n");
   1653 
   1654 	$endianness = 1;
   1655 
   1656 	unpack_int32($packed_word, $endianness) == $BBG_FILE_MAGIC
   1657 		or die("ERROR: bbg file magic does not match\n");
   1658 
   1659 	# Skip version
   1660 	seek(INPUT, 4, 1);
   1661 
   1662 	# Read data in words of 4 bytes
   1663 	while (read(INPUT, $packed_word, 4) == 4)
   1664 	{
   1665 		# Get record tag
   1666 		$value = unpack_int32($packed_word, $endianness);
   1667 
   1668 		# Get record length
   1669 		read(INPUT, $packed_word, 4);
   1670 		$length = unpack_int32($packed_word, $endianness);
   1671 
   1672 		if ($value == $GCNO_FUNCTION_TAG)
   1673 		{
   1674 			# Get function name
   1675 			($value, $function_name) =
   1676 				read_hammer_bbg_string(*INPUT, $endianness);
   1677 			$function_name =~ s/\W/_/g;
   1678 			$filename = undef;
   1679 			$first_line = undef;
   1680 
   1681 			seek(INPUT, $length - $value * 4, 1);
   1682 		}
   1683 		elsif ($value == $GCNO_LINES_TAG)
   1684 		{
   1685 			# Get linenumber and filename
   1686 			# Skip block number
   1687 			seek(INPUT, 4, 1);
   1688 			$length -= 4;
   1689 
   1690 			while ($length > 0)
   1691 			{
   1692 				read(INPUT, $packed_word, 4);
   1693 				$lineno = unpack_int32($packed_word,
   1694 						       $endianness);
   1695 				$length -= 4;
   1696 				if ($lineno != 0)
   1697 				{
   1698 					if (!defined($first_line))
   1699 					{
   1700 						$first_line = $lineno;
   1701 					}
   1702 					if (defined($filename))
   1703 					{
   1704 						$result{$filename} .=
   1705 							($result{$filename} ? "," : "").
   1706 							"=$lineno";
   1707 					}
   1708 					else
   1709 					{
   1710 						warn("WARNING: unassigned line".
   1711 						     " number in .bbg file ".
   1712 						     "$bbg_filename\n");
   1713 					}
   1714 					next;
   1715 				}
   1716 				($blocks, $value) =
   1717 					read_hammer_bbg_string(
   1718 						*INPUT, $endianness);
   1719 				# Add all filenames to result list
   1720 				if (defined($value))
   1721 				{
   1722 					$value = solve_relative_path(
   1723 							$base_dir, $value);
   1724 					if (!defined($result{$value}))
   1725 					{
   1726 						$result{$value} = undef;
   1727 					}
   1728 					if (!defined($filename))
   1729 					{
   1730 						$filename = $value;
   1731 					}
   1732 				}
   1733 				$length -= $blocks * 4;
   1734 
   1735 				# Got a complete data set?
   1736 				if (defined($filename) &&
   1737 				    defined($first_line) &&
   1738 				    defined($function_name))
   1739 				{
   1740 					# Add it to our result hash
   1741 					if (defined($result{$filename}))
   1742 					{
   1743 						$result{$filename} .=
   1744 						",$function_name=$first_line";
   1745 					}
   1746 					else
   1747 					{
   1748 						$result{$filename} =
   1749 						"$function_name=$first_line";
   1750 					}
   1751 					$function_name = undef;
   1752 					$filename = undef;
   1753 					$first_line = undef;
   1754 				}
   1755 			}
   1756 		}
   1757 		else
   1758 		{
   1759 			# Skip other records
   1760 			seek(INPUT, $length, 1);
   1761 		}
   1762 	}
   1763 	close(INPUT);
   1764 
   1765 	if (!scalar(keys(%result)))
   1766 	{
   1767 		die("ERROR: no data found in $bbg_filename!\n");
   1768 	}
   1769 	return %result;
   1770 }
   1771 
   1772 
   1773 #
   1774 # read_hammer_bbg_string(handle, endianness);
   1775 #
   1776 # Read a string in 4-byte chunks from HANDLE.
   1777 #
   1778 # Return (number of 4-byte chunks read, string).
   1779 #
   1780 
   1781 sub read_hammer_bbg_string(*$)
   1782 {
   1783 	my $handle		= $_[0];
   1784 	my $endianness		= $_[1];
   1785 	my $length		= 0;
   1786 	my $string		= "";
   1787 	my $packed_word;
   1788 	my $pad;
   1789 
   1790 	read($handle, $packed_word, 4) == 4
   1791 		or die("ERROR: reading string\n");
   1792 
   1793 	$length = unpack_int32($packed_word, $endianness);
   1794 	$pad = 4 - $length % 4;
   1795 
   1796 	if ($length == 0)
   1797 	{
   1798 		return (1, undef);
   1799 	}
   1800 
   1801 	read($handle, $string, $length) ==
   1802 		$length or die("ERROR: reading string\n");
   1803 	seek($handle, $pad, 1);
   1804 
   1805 	return(1 + ($length + $pad) / 4, $string);
   1806 }
   1807 
   1808 #
   1809 # unpack_int32(word, endianness)
   1810 #
   1811 # Interpret 4-byte binary string WORD as signed 32 bit integer in
   1812 # endian encoding defined by ENDIANNESS (0=little, 1=big) and return its
   1813 # value.
   1814 #
   1815 
   1816 sub unpack_int32($$)
   1817 {
   1818 	return sprintf("%d", unpack($_[1] ? "N" : "V",$_[0]));
   1819 }
   1820 
   1821 
   1822 #
   1823 # Get the GCOV tool version. Return an integer number which represents the
   1824 # GCOV version. Version numbers can be compared using standard integer
   1825 # operations.
   1826 #
   1827 
   1828 sub get_gcov_version()
   1829 {
   1830 	local *HANDLE;
   1831 	my $version_string;
   1832 	my $result;
   1833 
   1834 	open(GCOV_PIPE, "$gcov_tool -v |")
   1835 		or die("ERROR: cannot retrieve gcov version!\n");
   1836 	$version_string = <GCOV_PIPE>;
   1837 	close(GCOV_PIPE);
   1838 
   1839 	$result = 0;
   1840 	if ($version_string =~ /(\d+)\.(\d+)(\.(\d+))?/)
   1841 	{
   1842 		if (defined($4))
   1843 		{
   1844 			info("Found gcov version: $1.$2.$4\n");
   1845 			$result = $1 << 16 | $2 << 8 | $4;
   1846 		}
   1847 		else
   1848 		{
   1849 			info("Found gcov version: $1.$2\n");
   1850 			$result = $1 << 16 | $2 << 8;
   1851 		}
   1852 	}
   1853         if ($version_string =~ /suse/i && $result == 0x30303 ||
   1854             $version_string =~ /mandrake/i && $result == 0x30302)
   1855 	{
   1856 		info("Using compatibility mode for GCC 3.3 (hammer)\n");
   1857 		$compatibility = $COMPAT_HAMMER;
   1858 	}
   1859 	return $result;
   1860 }
   1861 
   1862 
   1863 #
   1864 # info(printf_parameter)
   1865 #
   1866 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag
   1867 # is not set.
   1868 #
   1869 
   1870 sub info(@)
   1871 {
   1872 	if (!$quiet)
   1873 	{
   1874 		# Print info string
   1875 		if (defined($output_filename) && ($output_filename eq "-"))
   1876 		{
   1877 			# Don't interfere with the .info output to STDOUT
   1878 			printf(STDERR @_);
   1879 		}
   1880 		else
   1881 		{
   1882 			printf(@_);
   1883 		}
   1884 	}
   1885 }
   1886 
   1887 
   1888 #
   1889 # int_handler()
   1890 #
   1891 # Called when the script was interrupted by an INT signal (e.g. CTRl-C)
   1892 #
   1893 
   1894 sub int_handler()
   1895 {
   1896 	if ($cwd) { chdir($cwd); }
   1897 	info("Aborted.\n");
   1898 	exit(1);
   1899 }
   1900 
   1901 
   1902 #
   1903 # system_no_output(mode, parameters)
   1904 #
   1905 # Call an external program using PARAMETERS while suppressing depending on
   1906 # the value of MODE:
   1907 #
   1908 #   MODE & 1: suppress STDOUT
   1909 #   MODE & 2: suppress STDERR
   1910 #
   1911 # Return 0 on success, non-zero otherwise.
   1912 #
   1913 
   1914 sub system_no_output($@)
   1915 {
   1916 	my $mode = shift;
   1917 	my $result;
   1918 	local *OLD_STDERR;
   1919 	local *OLD_STDOUT;
   1920 
   1921 	# Save old stdout and stderr handles
   1922 	($mode & 1) && open(OLD_STDOUT, ">>&STDOUT");
   1923 	($mode & 2) && open(OLD_STDERR, ">>&STDERR");
   1924 
   1925 	# Redirect to /dev/null
   1926 	($mode & 1) && open(STDOUT, ">/dev/null");
   1927 	($mode & 2) && open(STDERR, ">/dev/null");
   1928  
   1929 	system(@_);
   1930 	$result = $?;
   1931 
   1932 	# Close redirected handles
   1933 	($mode & 1) && close(STDOUT);
   1934 	($mode & 2) && close(STDERR);
   1935 
   1936 	# Restore old handles
   1937 	($mode & 1) && open(STDOUT, ">>&OLD_STDOUT");
   1938 	($mode & 2) && open(STDERR, ">>&OLD_STDERR");
   1939  
   1940 	return $result;
   1941 }
   1942 
   1943 
   1944 #
   1945 # read_config(filename)
   1946 #
   1947 # Read configuration file FILENAME and return a reference to a hash containing
   1948 # all valid key=value pairs found.
   1949 #
   1950 
   1951 sub read_config($)
   1952 {
   1953 	my $filename = $_[0];
   1954 	my %result;
   1955 	my $key;
   1956 	my $value;
   1957 	local *HANDLE;
   1958 
   1959 	if (!open(HANDLE, "<$filename"))
   1960 	{
   1961 		warn("WARNING: cannot read configuration file $filename\n");
   1962 		return undef;
   1963 	}
   1964 	while (<HANDLE>)
   1965 	{
   1966 		chomp;
   1967 		# Skip comments
   1968 		s/#.*//;
   1969 		# Remove leading blanks
   1970 		s/^\s+//;
   1971 		# Remove trailing blanks
   1972 		s/\s+$//;
   1973 		next unless length;
   1974 		($key, $value) = split(/\s*=\s*/, $_, 2);
   1975 		if (defined($key) && defined($value))
   1976 		{
   1977 			$result{$key} = $value;
   1978 		}
   1979 		else
   1980 		{
   1981 			warn("WARNING: malformed statement in line $. ".
   1982 			     "of configuration file $filename\n");
   1983 		}
   1984 	}
   1985 	close(HANDLE);
   1986 	return \%result;
   1987 }
   1988 
   1989 
   1990 #
   1991 # apply_config(REF)
   1992 #
   1993 # REF is a reference to a hash containing the following mapping:
   1994 #
   1995 #   key_string => var_ref
   1996 #
   1997 # where KEY_STRING is a keyword and VAR_REF is a reference to an associated
   1998 # variable. If the global configuration hash CONFIG contains a value for
   1999 # keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. 
   2000 #
   2001 
   2002 sub apply_config($)
   2003 {
   2004 	my $ref = $_[0];
   2005 
   2006 	foreach (keys(%{$ref}))
   2007 	{
   2008 		if (defined($config->{$_}))
   2009 		{
   2010 			${$ref->{$_}} = $config->{$_};
   2011 		}
   2012 	}
   2013 }
   2014 
   2015 
   2016 sub gen_initial_info($)
   2017 {
   2018 	my $directory = $_[0];
   2019 	my @file_list;
   2020 
   2021 	if (-d $directory)
   2022 	{
   2023 		info("Scanning $directory for $graph_file_extension ".
   2024 		     "files ...\n");	
   2025 
   2026 		@file_list = `find "$directory" $maxdepth $follow -name \\*$graph_file_extension -type f 2>/dev/null`;
   2027 		chomp(@file_list);
   2028 		@file_list or die("ERROR: no $graph_file_extension files ".
   2029 				  "found in $directory!\n");
   2030 		info("Found %d graph files in %s\n", $#file_list+1, $directory);
   2031 	}
   2032 	else
   2033 	{
   2034 		@file_list = ($directory);
   2035 	}
   2036 
   2037 	# Process all files in list
   2038 	foreach (@file_list) { process_graphfile($_); }
   2039 }
   2040 
   2041 sub process_graphfile($)
   2042 {
   2043 	my $graph_filename = $_[0];
   2044 	my $graph_dir;
   2045 	my $graph_basename;
   2046 	my $source_dir;
   2047 	my $base_dir;
   2048 	my %graph_data;
   2049 	my $filename;
   2050 	local *INFO_HANDLE;
   2051 
   2052 	info("Processing $_[0]\n");
   2053 
   2054 	# Get path to data file in absolute and normalized form (begins with /,
   2055 	# contains no more ../ or ./)
   2056 	$graph_filename = solve_relative_path($cwd, $graph_filename);
   2057 
   2058 	# Get directory and basename of data file
   2059 	($graph_dir, $graph_basename) = split_filename($graph_filename);
   2060 
   2061 	# avoid files from .libs dirs 	 
   2062 	if ($compat_libtool && $graph_dir =~ m/(.*)\/\.libs$/) {
   2063 		$source_dir = $1;
   2064 	} else {
   2065 		$source_dir = $graph_dir;
   2066 	}
   2067 
   2068 	# Construct base_dir for current file
   2069 	if ($base_directory)
   2070 	{
   2071 		$base_dir = $base_directory;
   2072 	}
   2073 	else
   2074 	{
   2075 		$base_dir = $source_dir;
   2076 	}
   2077 
   2078 	if ($gcov_version < $GCOV_VERSION_3_4_0)
   2079 	{
   2080 		if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER)
   2081 		{
   2082 			%graph_data = read_hammer_bbg_file($graph_filename,
   2083 							   $base_dir);
   2084 		}
   2085 		else
   2086 		{
   2087 			%graph_data = read_bb_file($graph_filename, $base_dir);
   2088 		}
   2089 	} 
   2090 	else
   2091 	{
   2092 		%graph_data = read_gcno_file($graph_filename, $base_dir);
   2093 	}
   2094 
   2095 	# Check whether we're writing to a single file
   2096 	if ($output_filename)
   2097 	{
   2098 		if ($output_filename eq "-")
   2099 		{
   2100 			*INFO_HANDLE = *STDOUT;
   2101 		}
   2102 		else
   2103 		{
   2104 			# Append to output file
   2105 			open(INFO_HANDLE, ">>$output_filename")
   2106 				or die("ERROR: cannot write to ".
   2107 				       "$output_filename!\n");
   2108 		}
   2109 	}
   2110 	else
   2111 	{
   2112 		# Open .info file for output
   2113 		open(INFO_HANDLE, ">$graph_filename.info")
   2114 			or die("ERROR: cannot create $graph_filename.info!\n");
   2115 	}
   2116 
   2117 	# Write test name
   2118 	printf(INFO_HANDLE "TN:%s\n", $test_name);
   2119 	foreach $filename (keys(%graph_data))
   2120 	{
   2121 		my %lines;
   2122 		my $count = 0;
   2123 		my @functions;
   2124 
   2125 		print(INFO_HANDLE "SF:$filename\n");
   2126 
   2127 		# Write function related data
   2128 		foreach (split(",",$graph_data{$filename}))
   2129 		{
   2130 			my ($fn, $line) = split("=", $_);
   2131 
   2132 			if ($fn eq "")
   2133 			{
   2134 				$lines{$line} = "";
   2135 				next;
   2136 			}
   2137 
   2138 			# Normalize function name
   2139 			$fn =~ s/\W/_/g;
   2140 
   2141 			print(INFO_HANDLE "FN:$line,$fn\n");
   2142 			push(@functions, $fn);
   2143 		}
   2144 		foreach (@functions) {
   2145 			print(INFO_HANDLE "FNDA:$_,0\n");
   2146 		}
   2147 		print(INFO_HANDLE "FNF:".scalar(@functions)."\n");
   2148 		print(INFO_HANDLE "FNH:0\n");
   2149 
   2150 		# Write line related data
   2151 		foreach (sort {$a <=> $b } keys(%lines))
   2152 		{
   2153 			print(INFO_HANDLE "DA:$_,0\n");
   2154 			$count++;
   2155 		}
   2156 		print(INFO_HANDLE "LH:0\n");
   2157 		print(INFO_HANDLE "LF:$count\n");
   2158 		print(INFO_HANDLE "end_of_record\n");
   2159 	}
   2160 	if (!($output_filename && ($output_filename eq "-")))
   2161 	{
   2162 		close(INFO_HANDLE);
   2163 	}
   2164 }
   2165 
   2166 sub warn_handler($)
   2167 {
   2168 	my ($msg) = @_;
   2169 
   2170 	warn("$tool_name: $msg");
   2171 }
   2172 
   2173 sub die_handler($)
   2174 {
   2175 	my ($msg) = @_;
   2176 
   2177 	die("$tool_name: $msg");
   2178 }
   2179