Index: third_party/lcov/bin/geninfo |
=================================================================== |
--- third_party/lcov/bin/geninfo (revision 0) |
+++ third_party/lcov/bin/geninfo (revision 0) |
@@ -0,0 +1,2178 @@ |
+#!/usr/bin/perl -w |
+# |
+# Copyright (c) International Business Machines Corp., 2002,2007 |
+# |
+# This program is free software; you can redistribute it and/or modify |
+# it under the terms of the GNU General Public License as published by |
+# the Free Software Foundation; either version 2 of the License, or (at |
+# your option) any later version. |
+# |
+# This program is distributed in the hope that it will be useful, but |
+# WITHOUT ANY WARRANTY; without even the implied warranty of |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
+# General Public License for more details. |
+# |
+# You should have received a copy of the GNU General Public License |
+# along with this program; if not, write to the Free Software |
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
+# |
+# |
+# geninfo |
+# |
+# This script generates .info files from data files as created by code |
+# instrumented with gcc's built-in profiling mechanism. Call it with |
+# --help and refer to the geninfo man page to get information on usage |
+# and available options. |
+# |
+# |
+# Authors: |
+# 2002-08-23 created by Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com> |
+# IBM Lab Boeblingen |
+# based on code by Manoj Iyer <manjo@mail.utexas.edu> and |
+# Megan Bock <mbock@us.ibm.com> |
+# IBM Austin |
+# 2002-09-05 / Peter Oberparleiter: implemented option that allows file list |
+# 2003-04-16 / Peter Oberparleiter: modified read_gcov so that it can also |
+# parse the new gcov format which is to be introduced in gcc 3.3 |
+# 2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT |
+# 2003-07-03 / Peter Oberparleiter: added line checksum support, added |
+# --no-checksum |
+# 2003-09-18 / Nigel Hinds: capture branch coverage data from GCOV |
+# 2003-12-11 / Laurent Deniel: added --follow option |
+# workaround gcov (<= 3.2.x) bug with empty .da files |
+# 2004-01-03 / Laurent Deniel: Ignore empty .bb files |
+# 2004-02-16 / Andreas Krebbel: Added support for .gcno/.gcda files and |
+# gcov versioning |
+# 2004-08-09 / Peter Oberparleiter: added configuration file support |
+# 2008-07-14 / Tom Zoerner: added --function-coverage command line option |
+# 2008-08-13 / Peter Oberparleiter: modified function coverage |
+# implementation (now enabled per default) |
+# |
+ |
+use strict; |
+use File::Basename; |
+use Getopt::Long; |
+use Digest::MD5 qw(md5_base64); |
+ |
+ |
+# Constants |
+our $lcov_version = "LCOV version 1.7"; |
+our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; |
+our $gcov_tool = "gcov"; |
+our $tool_name = basename($0); |
+ |
+our $GCOV_VERSION_3_4_0 = 0x30400; |
+our $GCOV_VERSION_3_3_0 = 0x30300; |
+our $GCNO_FUNCTION_TAG = 0x01000000; |
+our $GCNO_LINES_TAG = 0x01450000; |
+our $GCNO_FILE_MAGIC = 0x67636e6f; |
+our $BBG_FILE_MAGIC = 0x67626267; |
+ |
+our $COMPAT_HAMMER = "hammer"; |
+ |
+our $ERROR_GCOV = 0; |
+our $ERROR_SOURCE = 1; |
+ |
+# Prototypes |
+sub print_usage(*); |
+sub gen_info($); |
+sub process_dafile($); |
+sub match_filename($@); |
+sub solve_ambiguous_match($$$); |
+sub split_filename($); |
+sub solve_relative_path($$); |
+sub get_dir($); |
+sub read_gcov_header($); |
+sub read_gcov_file($); |
+sub read_bb_file($$); |
+sub read_string(*$); |
+sub read_gcno_file($$); |
+sub read_gcno_string(*$); |
+sub read_hammer_bbg_file($$); |
+sub read_hammer_bbg_string(*$); |
+sub unpack_int32($$); |
+sub info(@); |
+sub get_gcov_version(); |
+sub system_no_output($@); |
+sub read_config($); |
+sub apply_config($); |
+sub gen_initial_info($); |
+sub process_graphfile($); |
+sub warn_handler($); |
+sub die_handler($); |
+ |
+# Global variables |
+our $gcov_version; |
+our $graph_file_extension; |
+our $data_file_extension; |
+our @data_directory; |
+our $test_name = ""; |
+our $quiet; |
+our $help; |
+our $output_filename; |
+our $base_directory; |
+our $version; |
+our $follow; |
+our $checksum; |
+our $no_checksum; |
+our $preserve_paths; |
+our $compat_libtool; |
+our $no_compat_libtool; |
+our $adjust_testname; |
+our $config; # Configuration file contents |
+our $compatibility; # Compatibility version flag - used to indicate |
+ # non-standard GCOV data format versions |
+our @ignore_errors; # List of errors to ignore (parameter) |
+our @ignore; # List of errors to ignore (array) |
+our $initial; |
+our $no_recursion = 0; |
+our $maxdepth; |
+ |
+our $cwd = `pwd`; |
+chomp($cwd); |
+ |
+ |
+# |
+# Code entry point |
+# |
+ |
+# Register handler routine to be called when interrupted |
+$SIG{"INT"} = \&int_handler; |
+$SIG{__WARN__} = \&warn_handler; |
+$SIG{__DIE__} = \&die_handler; |
+ |
+# Read configuration file if available |
+if (-r $ENV{"HOME"}."/.lcovrc") |
+{ |
+ $config = read_config($ENV{"HOME"}."/.lcovrc"); |
+} |
+elsif (-r "/etc/lcovrc") |
+{ |
+ $config = read_config("/etc/lcovrc"); |
+} |
+ |
+if ($config) |
+{ |
+ # Copy configuration file values to variables |
+ apply_config({ |
+ "geninfo_gcov_tool" => \$gcov_tool, |
+ "geninfo_adjust_testname" => \$adjust_testname, |
+ "geninfo_checksum" => \$checksum, |
+ "geninfo_no_checksum" => \$no_checksum, # deprecated |
+ "geninfo_compat_libtool" => \$compat_libtool}); |
+ |
+ # Merge options |
+ if (defined($no_checksum)) |
+ { |
+ $checksum = ($no_checksum ? 0 : 1); |
+ $no_checksum = undef; |
+ } |
+} |
+ |
+# Parse command line options |
+if (!GetOptions("test-name=s" => \$test_name, |
+ "output-filename=s" => \$output_filename, |
+ "checksum" => \$checksum, |
+ "no-checksum" => \$no_checksum, |
+ "base-directory=s" => \$base_directory, |
+ "version" =>\$version, |
+ "quiet" => \$quiet, |
+ "help|?" => \$help, |
+ "follow" => \$follow, |
+ "compat-libtool" => \$compat_libtool, |
+ "no-compat-libtool" => \$no_compat_libtool, |
+ "gcov-tool=s" => \$gcov_tool, |
+ "ignore-errors=s" => \@ignore_errors, |
+ "initial|i" => \$initial, |
+ "no-recursion" => \$no_recursion, |
+ )) |
+{ |
+ print(STDERR "Use $tool_name --help to get usage information\n"); |
+ exit(1); |
+} |
+else |
+{ |
+ # Merge options |
+ if (defined($no_checksum)) |
+ { |
+ $checksum = ($no_checksum ? 0 : 1); |
+ $no_checksum = undef; |
+ } |
+ |
+ if (defined($no_compat_libtool)) |
+ { |
+ $compat_libtool = ($no_compat_libtool ? 0 : 1); |
+ $no_compat_libtool = undef; |
+ } |
+} |
+ |
+@data_directory = @ARGV; |
+ |
+# Check for help option |
+if ($help) |
+{ |
+ print_usage(*STDOUT); |
+ exit(0); |
+} |
+ |
+# Check for version option |
+if ($version) |
+{ |
+ print("$tool_name: $lcov_version\n"); |
+ exit(0); |
+} |
+ |
+# Make sure test names only contain valid characters |
+if ($test_name =~ s/\W/_/g) |
+{ |
+ warn("WARNING: invalid characters removed from testname!\n"); |
+} |
+ |
+# Adjust test name to include uname output if requested |
+if ($adjust_testname) |
+{ |
+ $test_name .= "__".`uname -a`; |
+ $test_name =~ s/\W/_/g; |
+} |
+ |
+# Make sure base_directory contains an absolute path specification |
+if ($base_directory) |
+{ |
+ $base_directory = solve_relative_path($cwd, $base_directory); |
+} |
+ |
+# Check for follow option |
+if ($follow) |
+{ |
+ $follow = "-follow" |
+} |
+else |
+{ |
+ $follow = ""; |
+} |
+ |
+# Determine checksum mode |
+if (defined($checksum)) |
+{ |
+ # Normalize to boolean |
+ $checksum = ($checksum ? 1 : 0); |
+} |
+else |
+{ |
+ # Default is off |
+ $checksum = 0; |
+} |
+ |
+# Determine libtool compatibility mode |
+if (defined($compat_libtool)) |
+{ |
+ $compat_libtool = ($compat_libtool? 1 : 0); |
+} |
+else |
+{ |
+ # Default is on |
+ $compat_libtool = 1; |
+} |
+ |
+# Determine max depth for recursion |
+if ($no_recursion) |
+{ |
+ $maxdepth = "-maxdepth 1"; |
+} |
+else |
+{ |
+ $maxdepth = ""; |
+} |
+ |
+# Check for directory name |
+if (!@data_directory) |
+{ |
+ die("No directory specified\n". |
+ "Use $tool_name --help to get usage information\n"); |
+} |
+else |
+{ |
+ foreach (@data_directory) |
+ { |
+ stat($_); |
+ if (!-r _) |
+ { |
+ die("ERROR: cannot read $_!\n"); |
+ } |
+ } |
+} |
+ |
+if (@ignore_errors) |
+{ |
+ my @expanded; |
+ my $error; |
+ |
+ # Expand comma-separated entries |
+ foreach (@ignore_errors) { |
+ if (/,/) |
+ { |
+ push(@expanded, split(",", $_)); |
+ } |
+ else |
+ { |
+ push(@expanded, $_); |
+ } |
+ } |
+ |
+ foreach (@expanded) |
+ { |
+ /^gcov$/ && do { $ignore[$ERROR_GCOV] = 1; next; } ; |
+ /^source$/ && do { $ignore[$ERROR_SOURCE] = 1; next; }; |
+ die("ERROR: unknown argument for --ignore-errors: $_\n"); |
+ } |
+} |
+ |
+if (system_no_output(3, $gcov_tool, "--help") == -1) |
+{ |
+ die("ERROR: need tool $gcov_tool!\n"); |
+} |
+ |
+$gcov_version = get_gcov_version(); |
+ |
+if ($gcov_version < $GCOV_VERSION_3_4_0) |
+{ |
+ if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) |
+ { |
+ $data_file_extension = ".da"; |
+ $graph_file_extension = ".bbg"; |
+ } |
+ else |
+ { |
+ $data_file_extension = ".da"; |
+ $graph_file_extension = ".bb"; |
+ } |
+} |
+else |
+{ |
+ $data_file_extension = ".gcda"; |
+ $graph_file_extension = ".gcno"; |
+} |
+ |
+# Check for availability of --preserve-paths option of gcov |
+if (`$gcov_tool --help` =~ /--preserve-paths/) |
+{ |
+ $preserve_paths = "--preserve-paths"; |
+} |
+ |
+# Check output filename |
+if (defined($output_filename) && ($output_filename ne "-")) |
+{ |
+ # Initially create output filename, data is appended |
+ # for each data file processed |
+ local *DUMMY_HANDLE; |
+ open(DUMMY_HANDLE, ">$output_filename") |
+ or die("ERROR: cannot create $output_filename!\n"); |
+ close(DUMMY_HANDLE); |
+ |
+ # Make $output_filename an absolute path because we're going |
+ # to change directories while processing files |
+ if (!($output_filename =~ /^\/(.*)$/)) |
+ { |
+ $output_filename = $cwd."/".$output_filename; |
+ } |
+} |
+ |
+# Do something |
+if ($initial) |
+{ |
+ foreach (@data_directory) |
+ { |
+ gen_initial_info($_); |
+ } |
+} |
+else |
+{ |
+ foreach (@data_directory) |
+ { |
+ gen_info($_); |
+ } |
+} |
+info("Finished .info-file creation\n"); |
+ |
+exit(0); |
+ |
+ |
+ |
+# |
+# print_usage(handle) |
+# |
+# Print usage information. |
+# |
+ |
+sub print_usage(*) |
+{ |
+ local *HANDLE = $_[0]; |
+ |
+ print(HANDLE <<END_OF_USAGE); |
+Usage: $tool_name [OPTIONS] DIRECTORY |
+ |
+Traverse DIRECTORY and create a .info file for each data file found. Note |
+that you may specify more than one directory, all of which are then processed |
+sequentially. |
+ |
+ -h, --help Print this help, then exit |
+ -v, --version Print version number, then exit |
+ -q, --quiet Do not print progress messages |
+ -i, --initial Capture initial zero coverage data |
+ -t, --test-name NAME Use test case name NAME for resulting data |
+ -o, --output-filename OUTFILE Write data only to OUTFILE |
+ -f, --follow Follow links when searching .da/.gcda files |
+ -b, --base-directory DIR Use DIR as base directory for relative paths |
+ --(no-)checksum Enable (disable) line checksumming |
+ --(no-)compat-libtool Enable (disable) libtool compatibility mode |
+ --gcov-tool TOOL Specify gcov tool location |
+ --ignore-errors ERROR Continue after ERROR (gcov, source) |
+ --no-recursion Exlude subdirectories from processing |
+ --function-coverage Capture function call counts |
+ |
+For more information see: $lcov_url |
+END_OF_USAGE |
+ ; |
+} |
+ |
+ |
+# |
+# gen_info(directory) |
+# |
+# Traverse DIRECTORY and create a .info file for each data file found. |
+# The .info file contains TEST_NAME in the following format: |
+# |
+# TN:<test name> |
+# |
+# For each source file name referenced in the data file, there is a section |
+# containing source code and coverage data: |
+# |
+# SF:<absolute path to the source file> |
+# FN:<line number of function start>,<function name> for each function |
+# DA:<line number>,<execution count> for each instrumented line |
+# LH:<number of lines with an execution count> greater than 0 |
+# LF:<number of instrumented lines> |
+# |
+# Sections are separated by: |
+# |
+# end_of_record |
+# |
+# In addition to the main source code file there are sections for each |
+# #included file containing executable code. Note that the absolute path |
+# of a source file is generated by interpreting the contents of the respective |
+# graph file. Relative filenames are prefixed with the directory in which the |
+# graph file is found. Note also that symbolic links to the graph file will be |
+# resolved so that the actual file path is used instead of the path to a link. |
+# This approach is necessary for the mechanism to work with the /proc/gcov |
+# files. |
+# |
+# Die on error. |
+# |
+ |
+sub gen_info($) |
+{ |
+ my $directory = $_[0]; |
+ my @file_list; |
+ |
+ if (-d $directory) |
+ { |
+ info("Scanning $directory for $data_file_extension ". |
+ "files ...\n"); |
+ |
+ @file_list = `find "$directory" $maxdepth $follow -name \\*$data_file_extension -type f 2>/dev/null`; |
+ chomp(@file_list); |
+ @file_list or die("ERROR: no $data_file_extension files found ". |
+ "in $directory!\n"); |
+ info("Found %d data files in %s\n", $#file_list+1, $directory); |
+ } |
+ else |
+ { |
+ @file_list = ($directory); |
+ } |
+ |
+ # Process all files in list |
+ foreach (@file_list) { process_dafile($_); } |
+} |
+ |
+ |
+# |
+# process_dafile(da_filename) |
+# |
+# Create a .info file for a single data file. |
+# |
+# Die on error. |
+# |
+ |
+sub process_dafile($) |
+{ |
+ info("Processing %s\n", $_[0]); |
+ |
+ my $da_filename; # Name of data file to process |
+ my $da_dir; # Directory of data file |
+ my $source_dir; # Directory of source file |
+ my $da_basename; # data filename without ".da/.gcda" extension |
+ my $bb_filename; # Name of respective graph file |
+ my %bb_content; # Contents of graph file |
+ my $gcov_error; # Error code of gcov tool |
+ my $object_dir; # Directory containing all object files |
+ my $source_filename; # Name of a source code file |
+ my $gcov_file; # Name of a .gcov file |
+ my @gcov_content; # Content of a .gcov file |
+ my @gcov_branches; # Branch content of a .gcov file |
+ my @gcov_functions; # Function calls of a .gcov file |
+ my @gcov_list; # List of generated .gcov files |
+ my $line_number; # Line number count |
+ my $lines_hit; # Number of instrumented lines hit |
+ my $lines_found; # Number of instrumented lines found |
+ my $funcs_hit; # Number of instrumented functions hit |
+ my $funcs_found; # Number of instrumented functions found |
+ my $source; # gcov source header information |
+ my $object; # gcov object header information |
+ my @matches; # List of absolute paths matching filename |
+ my @unprocessed; # List of unprocessed source code files |
+ my $base_dir; # Base directory for current file |
+ my @result; |
+ my $index; |
+ my $da_renamed; # If data file is to be renamed |
+ local *INFO_HANDLE; |
+ |
+ # Get path to data file in absolute and normalized form (begins with /, |
+ # contains no more ../ or ./) |
+ $da_filename = solve_relative_path($cwd, $_[0]); |
+ |
+ # Get directory and basename of data file |
+ ($da_dir, $da_basename) = split_filename($da_filename); |
+ |
+ # avoid files from .libs dirs |
+ if ($compat_libtool && $da_dir =~ m/(.*)\/\.libs$/) { |
+ $source_dir = $1; |
+ } else { |
+ $source_dir = $da_dir; |
+ } |
+ |
+ if (-z $da_filename) |
+ { |
+ $da_renamed = 1; |
+ } |
+ else |
+ { |
+ $da_renamed = 0; |
+ } |
+ |
+ # Construct base_dir for current file |
+ if ($base_directory) |
+ { |
+ $base_dir = $base_directory; |
+ } |
+ else |
+ { |
+ $base_dir = $source_dir; |
+ } |
+ |
+ # Check for writable $base_dir (gcov will try to write files there) |
+ stat($base_dir); |
+ if (!-w _) |
+ { |
+ die("ERROR: cannot write to directory $base_dir!\n"); |
+ } |
+ |
+ # Construct name of graph file |
+ $bb_filename = $da_dir."/".$da_basename.$graph_file_extension; |
+ |
+ # Find out the real location of graph file in case we're just looking at |
+ # a link |
+ while (readlink($bb_filename)) |
+ { |
+ my $last_dir = dirname($bb_filename); |
+ |
+ $bb_filename = readlink($bb_filename); |
+ $bb_filename = solve_relative_path($last_dir, $bb_filename); |
+ } |
+ |
+ # Ignore empty graph file (e.g. source file with no statement) |
+ if (-z $bb_filename) |
+ { |
+ warn("WARNING: empty $bb_filename (skipped)\n"); |
+ return; |
+ } |
+ |
+ # Read contents of graph file into hash. We need it later to find out |
+ # the absolute path to each .gcov file created as well as for |
+ # information about functions and their source code positions. |
+ if ($gcov_version < $GCOV_VERSION_3_4_0) |
+ { |
+ if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) |
+ { |
+ %bb_content = read_hammer_bbg_file($bb_filename, |
+ $base_dir); |
+ } |
+ else |
+ { |
+ %bb_content = read_bb_file($bb_filename, $base_dir); |
+ } |
+ } |
+ else |
+ { |
+ %bb_content = read_gcno_file($bb_filename, $base_dir); |
+ } |
+ |
+ # Set $object_dir to real location of object files. This may differ |
+ # from $da_dir if the graph file is just a link to the "real" object |
+ # file location. |
+ $object_dir = dirname($bb_filename); |
+ |
+ # Is the data file in a different directory? (this happens e.g. with |
+ # the gcov-kernel patch) |
+ if ($object_dir ne $da_dir) |
+ { |
+ # Need to create link to data file in $object_dir |
+ system("ln", "-s", $da_filename, |
+ "$object_dir/$da_basename$data_file_extension") |
+ and die ("ERROR: cannot create link $object_dir/". |
+ "$da_basename$data_file_extension!\n"); |
+ } |
+ |
+ # Change to directory containing data files and apply GCOV |
+ chdir($base_dir); |
+ |
+ if ($da_renamed) |
+ { |
+ # Need to rename empty data file to workaround |
+ # gcov <= 3.2.x bug (Abort) |
+ system_no_output(3, "mv", "$da_filename", "$da_filename.ori") |
+ and die ("ERROR: cannot rename $da_filename\n"); |
+ } |
+ |
+ # Execute gcov command and suppress standard output |
+ if ($preserve_paths) |
+ { |
+ $gcov_error = system_no_output(1, $gcov_tool, $da_filename, |
+ "-o", $object_dir, |
+ "--preserve-paths", |
+ "-b"); |
+ } |
+ else |
+ { |
+ $gcov_error = system_no_output(1, $gcov_tool, $da_filename, |
+ "-o", $object_dir, |
+ "-b"); |
+ } |
+ |
+ if ($da_renamed) |
+ { |
+ system_no_output(3, "mv", "$da_filename.ori", "$da_filename") |
+ and die ("ERROR: cannot rename $da_filename.ori"); |
+ } |
+ |
+ # Clean up link |
+ if ($object_dir ne $da_dir) |
+ { |
+ unlink($object_dir."/".$da_basename.$data_file_extension); |
+ } |
+ |
+ if ($gcov_error) |
+ { |
+ if ($ignore[$ERROR_GCOV]) |
+ { |
+ warn("WARNING: GCOV failed for $da_filename!\n"); |
+ return; |
+ } |
+ die("ERROR: GCOV failed for $da_filename!\n"); |
+ } |
+ |
+ # Collect data from resulting .gcov files and create .info file |
+ @gcov_list = glob("*.gcov"); |
+ |
+ # Check for files |
+ if (!@gcov_list) |
+ { |
+ warn("WARNING: gcov did not create any files for ". |
+ "$da_filename!\n"); |
+ } |
+ |
+ # Check whether we're writing to a single file |
+ if ($output_filename) |
+ { |
+ if ($output_filename eq "-") |
+ { |
+ *INFO_HANDLE = *STDOUT; |
+ } |
+ else |
+ { |
+ # Append to output file |
+ open(INFO_HANDLE, ">>$output_filename") |
+ or die("ERROR: cannot write to ". |
+ "$output_filename!\n"); |
+ } |
+ } |
+ else |
+ { |
+ # Open .info file for output |
+ open(INFO_HANDLE, ">$da_filename.info") |
+ or die("ERROR: cannot create $da_filename.info!\n"); |
+ } |
+ |
+ # Write test name |
+ printf(INFO_HANDLE "TN:%s\n", $test_name); |
+ |
+ # Traverse the list of generated .gcov files and combine them into a |
+ # single .info file |
+ @unprocessed = keys(%bb_content); |
+ foreach $gcov_file (@gcov_list) |
+ { |
+ ($source, $object) = read_gcov_header($gcov_file); |
+ |
+ if (defined($source)) |
+ { |
+ $source = solve_relative_path($base_dir, $source); |
+ } |
+ |
+ # gcov will happily create output even if there's no source code |
+ # available - this interferes with checksum creation so we need |
+ # to pull the emergency brake here. |
+ if (defined($source) && ! -r $source && $checksum) |
+ { |
+ if ($ignore[$ERROR_SOURCE]) |
+ { |
+ warn("WARNING: could not read source file ". |
+ "$source\n"); |
+ next; |
+ } |
+ die("ERROR: could not read source file $source\n"); |
+ } |
+ |
+ @matches = match_filename(defined($source) ? $source : |
+ $gcov_file, keys(%bb_content)); |
+ |
+ # Skip files that are not mentioned in the graph file |
+ if (!@matches) |
+ { |
+ warn("WARNING: cannot find an entry for ".$gcov_file. |
+ " in $graph_file_extension file, skipping ". |
+ "file!\n"); |
+ unlink($gcov_file); |
+ next; |
+ } |
+ |
+ # Read in contents of gcov file |
+ @result = read_gcov_file($gcov_file); |
+ @gcov_content = @{$result[0]}; |
+ @gcov_branches = @{$result[1]}; |
+ @gcov_functions = @{$result[2]}; |
+ |
+ # Skip empty files |
+ if (!@gcov_content) |
+ { |
+ warn("WARNING: skipping empty file ".$gcov_file."\n"); |
+ unlink($gcov_file); |
+ next; |
+ } |
+ |
+ if (scalar(@matches) == 1) |
+ { |
+ # Just one match |
+ $source_filename = $matches[0]; |
+ } |
+ else |
+ { |
+ # Try to solve the ambiguity |
+ $source_filename = solve_ambiguous_match($gcov_file, |
+ \@matches, \@gcov_content); |
+ } |
+ |
+ # Remove processed file from list |
+ for ($index = scalar(@unprocessed) - 1; $index >= 0; $index--) |
+ { |
+ if ($unprocessed[$index] eq $source_filename) |
+ { |
+ splice(@unprocessed, $index, 1); |
+ last; |
+ } |
+ } |
+ |
+ # Write absolute path of source file |
+ printf(INFO_HANDLE "SF:%s\n", $source_filename); |
+ |
+ # Write function-related information |
+ if (defined($bb_content{$source_filename})) |
+ { |
+ foreach (split(",",$bb_content{$source_filename})) |
+ { |
+ my ($fn, $line) = split("=", $_); |
+ |
+ if ($fn eq "") { |
+ next; |
+ } |
+ |
+ # Normalize function name |
+ $fn =~ s/\W/_/g; |
+ |
+ print(INFO_HANDLE "FN:$line,$fn\n"); |
+ } |
+ } |
+ |
+ #-- |
+ #-- FNDA: <call-count>, <function-name> |
+ #-- FNF: overall count of functions |
+ #-- FNH: overall count of functions with non-zero call count |
+ #-- |
+ $funcs_found = 0; |
+ $funcs_hit = 0; |
+ while (@gcov_functions) |
+ { |
+ printf(INFO_HANDLE "FNDA:%s,%s\n", |
+ $gcov_functions[0], |
+ $gcov_functions[1]); |
+ $funcs_found++; |
+ $funcs_hit++ if $gcov_functions[0]; |
+ splice(@gcov_functions,0,2); |
+ } |
+ if ($funcs_found > 0) { |
+ printf(INFO_HANDLE "FNF:%s\n", $funcs_found); |
+ printf(INFO_HANDLE "FNH:%s\n", $funcs_hit); |
+ } |
+ |
+ # Reset line counters |
+ $line_number = 0; |
+ $lines_found = 0; |
+ $lines_hit = 0; |
+ |
+ # Write coverage information for each instrumented line |
+ # Note: @gcov_content contains a list of (flag, count, source) |
+ # tuple for each source code line |
+ while (@gcov_content) |
+ { |
+ $line_number++; |
+ |
+ # Check for instrumented line |
+ if ($gcov_content[0]) |
+ { |
+ $lines_found++; |
+ printf(INFO_HANDLE "DA:".$line_number.",". |
+ $gcov_content[1].($checksum ? |
+ ",". md5_base64($gcov_content[2]) : ""). |
+ "\n"); |
+ |
+ # Increase $lines_hit in case of an execution |
+ # count>0 |
+ if ($gcov_content[1] > 0) { $lines_hit++; } |
+ } |
+ |
+ # Remove already processed data from array |
+ splice(@gcov_content,0,3); |
+ } |
+ |
+ #-- |
+ #-- BA: <code-line>, <branch-coverage> |
+ #-- |
+ #-- print one BA line for every branch of a |
+ #-- conditional. <branch-coverage> values |
+ #-- are: |
+ #-- 0 - not executed |
+ #-- 1 - executed but not taken |
+ #-- 2 - executed and taken |
+ #-- |
+ while (@gcov_branches) |
+ { |
+ if ($gcov_branches[0]) |
+ { |
+ printf(INFO_HANDLE "BA:%s,%s\n", |
+ $gcov_branches[0], |
+ $gcov_branches[1]); |
+ } |
+ splice(@gcov_branches,0,2); |
+ } |
+ |
+ # Write line statistics and section separator |
+ printf(INFO_HANDLE "LF:%s\n", $lines_found); |
+ printf(INFO_HANDLE "LH:%s\n", $lines_hit); |
+ print(INFO_HANDLE "end_of_record\n"); |
+ |
+ # Remove .gcov file after processing |
+ unlink($gcov_file); |
+ } |
+ |
+ # Check for files which show up in the graph file but were never |
+ # processed |
+ if (@unprocessed && @gcov_list) |
+ { |
+ foreach (@unprocessed) |
+ { |
+ warn("WARNING: no data found for $_\n"); |
+ } |
+ } |
+ |
+ if (!($output_filename && ($output_filename eq "-"))) |
+ { |
+ close(INFO_HANDLE); |
+ } |
+ |
+ # Change back to initial directory |
+ chdir($cwd); |
+} |
+ |
+ |
+# |
+# solve_relative_path(path, dir) |
+# |
+# Solve relative path components of DIR which, if not absolute, resides in PATH. |
+# |
+ |
+sub solve_relative_path($$) |
+{ |
+ my $path = $_[0]; |
+ my $dir = $_[1]; |
+ my $result; |
+ |
+ $result = $dir; |
+ # Prepend path if not absolute |
+ if ($dir =~ /^[^\/]/) |
+ { |
+ $result = "$path/$result"; |
+ } |
+ |
+ # Remove // |
+ $result =~ s/\/\//\//g; |
+ |
+ # Remove . |
+ $result =~ s/\/\.\//\//g; |
+ |
+ # Solve .. |
+ while ($result =~ s/\/[^\/]+\/\.\.\//\//) |
+ { |
+ } |
+ |
+ # Remove preceding .. |
+ $result =~ s/^\/\.\.\//\//g; |
+ |
+ return $result; |
+} |
+ |
+ |
+# |
+# match_filename(gcov_filename, list) |
+# |
+# Return a list of those entries of LIST which match the relative filename |
+# GCOV_FILENAME. |
+# |
+ |
+sub match_filename($@) |
+{ |
+ my $filename = shift; |
+ my @list = @_; |
+ my @result; |
+ |
+ $filename =~ s/^(.*).gcov$/$1/; |
+ |
+ if ($filename =~ /^\/(.*)$/) |
+ { |
+ $filename = "$1"; |
+ } |
+ |
+ foreach (@list) |
+ { |
+ if (/\/\Q$filename\E(.*)$/ && $1 eq "") |
+ { |
+ @result = (@result, $_); |
+ } |
+ } |
+ return @result; |
+} |
+ |
+ |
+# |
+# solve_ambiguous_match(rel_filename, matches_ref, gcov_content_ref) |
+# |
+# Try to solve ambiguous matches of mapping (gcov file) -> (source code) file |
+# by comparing source code provided in the GCOV file with that of the files |
+# in MATCHES. REL_FILENAME identifies the relative filename of the gcov |
+# file. |
+# |
+# Return the one real match or die if there is none. |
+# |
+ |
+sub solve_ambiguous_match($$$) |
+{ |
+ my $rel_name = $_[0]; |
+ my $matches = $_[1]; |
+ my $content = $_[2]; |
+ my $filename; |
+ my $index; |
+ my $no_match; |
+ local *SOURCE; |
+ |
+ # Check the list of matches |
+ foreach $filename (@$matches) |
+ { |
+ |
+ # Compare file contents |
+ open(SOURCE, $filename) |
+ or die("ERROR: cannot read $filename!\n"); |
+ |
+ $no_match = 0; |
+ for ($index = 2; <SOURCE>; $index += 3) |
+ { |
+ chomp; |
+ |
+ if ($_ ne @$content[$index]) |
+ { |
+ $no_match = 1; |
+ last; |
+ } |
+ } |
+ |
+ close(SOURCE); |
+ |
+ if (!$no_match) |
+ { |
+ info("Solved source file ambiguity for $rel_name\n"); |
+ return $filename; |
+ } |
+ } |
+ |
+ die("ERROR: could not match gcov data for $rel_name!\n"); |
+} |
+ |
+ |
+# |
+# split_filename(filename) |
+# |
+# Return (path, filename, extension) for a given FILENAME. |
+# |
+ |
+sub split_filename($) |
+{ |
+ my @path_components = split('/', $_[0]); |
+ my @file_components = split('\.', pop(@path_components)); |
+ my $extension = pop(@file_components); |
+ |
+ return (join("/",@path_components), join(".",@file_components), |
+ $extension); |
+} |
+ |
+ |
+# |
+# get_dir(filename); |
+# |
+# Return the directory component of a given FILENAME. |
+# |
+ |
+sub get_dir($) |
+{ |
+ my @components = split("/", $_[0]); |
+ pop(@components); |
+ |
+ return join("/", @components); |
+} |
+ |
+ |
+# |
+# read_gcov_header(gcov_filename) |
+# |
+# Parse file GCOV_FILENAME and return a list containing the following |
+# information: |
+# |
+# (source, object) |
+# |
+# where: |
+# |
+# source: complete relative path of the source code file (gcc >= 3.3 only) |
+# object: name of associated graph file |
+# |
+# Die on error. |
+# |
+ |
+sub read_gcov_header($) |
+{ |
+ my $source; |
+ my $object; |
+ local *INPUT; |
+ |
+ if (!open(INPUT, $_[0])) |
+ { |
+ if ($ignore_errors[$ERROR_GCOV]) |
+ { |
+ warn("WARNING: cannot read $_[0]!\n"); |
+ return (undef,undef); |
+ } |
+ die("ERROR: cannot read $_[0]!\n"); |
+ } |
+ |
+ while (<INPUT>) |
+ { |
+ chomp($_); |
+ |
+ if (/^\s+-:\s+0:Source:(.*)$/) |
+ { |
+ # Source: header entry |
+ $source = $1; |
+ } |
+ elsif (/^\s+-:\s+0:Object:(.*)$/) |
+ { |
+ # Object: header entry |
+ $object = $1; |
+ } |
+ else |
+ { |
+ last; |
+ } |
+ } |
+ |
+ close(INPUT); |
+ |
+ return ($source, $object); |
+} |
+ |
+ |
+# |
+# read_gcov_file(gcov_filename) |
+# |
+# Parse file GCOV_FILENAME (.gcov file format) and return the list: |
+# (reference to gcov_content, reference to gcov_branch, reference to gcov_func) |
+# |
+# gcov_content is a list of 3 elements |
+# (flag, count, source) for each source code line: |
+# |
+# $result[($line_number-1)*3+0] = instrumentation flag for line $line_number |
+# $result[($line_number-1)*3+1] = execution count for line $line_number |
+# $result[($line_number-1)*3+2] = source code text for line $line_number |
+# |
+# gcov_branch is a list of 2 elements |
+# (linenumber, branch result) for each branch |
+# |
+# gcov_func is a list of 2 elements |
+# (number of calls, function name) for each function |
+# |
+# Die on error. |
+# |
+ |
+sub read_gcov_file($) |
+{ |
+ my $filename = $_[0]; |
+ my @result = (); |
+ my @branches = (); |
+ my @functions = (); |
+ my $number; |
+ local *INPUT; |
+ |
+ open(INPUT, $filename) |
+ or die("ERROR: cannot read $filename!\n"); |
+ |
+ if ($gcov_version < $GCOV_VERSION_3_3_0) |
+ { |
+ # Expect gcov format as used in gcc < 3.3 |
+ while (<INPUT>) |
+ { |
+ chomp($_); |
+ |
+ if (/^\t\t(.*)$/) |
+ { |
+ # Uninstrumented line |
+ push(@result, 0); |
+ push(@result, 0); |
+ push(@result, $1); |
+ } |
+ elsif (/^branch/) |
+ { |
+ # Branch execution data |
+ push(@branches, scalar(@result) / 3); |
+ if (/^branch \d+ never executed$/) |
+ { |
+ push(@branches, 0); |
+ } |
+ elsif (/^branch \d+ taken = 0%/) |
+ { |
+ push(@branches, 1); |
+ } |
+ else |
+ { |
+ push(@branches, 2); |
+ } |
+ } |
+ elsif (/^call/ || /^function/) |
+ { |
+ # Function call return data |
+ } |
+ else |
+ { |
+ # Source code execution data |
+ $number = (split(" ",substr($_, 0, 16)))[0]; |
+ |
+ # Check for zero count which is indicated |
+ # by ###### |
+ if ($number eq "######") { $number = 0; } |
+ |
+ push(@result, 1); |
+ push(@result, $number); |
+ push(@result, substr($_, 16)); |
+ } |
+ } |
+ } |
+ else |
+ { |
+ # Expect gcov format as used in gcc >= 3.3 |
+ while (<INPUT>) |
+ { |
+ chomp($_); |
+ |
+ if (/^branch\s+\d+\s+(\S+)\s+(\S+)/) |
+ { |
+ # Branch execution data |
+ push(@branches, scalar(@result) / 3); |
+ if ($1 eq "never") |
+ { |
+ push(@branches, 0); |
+ } |
+ elsif ($2 eq "0%") |
+ { |
+ push(@branches, 1); |
+ } |
+ else |
+ { |
+ push(@branches, 2); |
+ } |
+ } |
+ elsif (/^function\s+(\S+)\s+called\s+(\d+)/) |
+ { |
+ push(@functions, $2, $1); |
+ } |
+ elsif (/^call/) |
+ { |
+ # Function call return data |
+ } |
+ elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/) |
+ { |
+ # <exec count>:<line number>:<source code> |
+ if ($2 eq "0") |
+ { |
+ # Extra data |
+ } |
+ elsif ($1 eq "-") |
+ { |
+ # Uninstrumented line |
+ push(@result, 0); |
+ push(@result, 0); |
+ push(@result, $3); |
+ } |
+ else |
+ { |
+ # Source code execution data |
+ $number = $1; |
+ |
+ # Check for zero count |
+ if ($number eq "#####") { $number = 0; } |
+ |
+ push(@result, 1); |
+ push(@result, $number); |
+ push(@result, $3); |
+ } |
+ } |
+ } |
+ } |
+ |
+ close(INPUT); |
+ return(\@result, \@branches, \@functions); |
+} |
+ |
+ |
+# |
+# read_bb_file(bb_filename, base_dir) |
+# |
+# Read .bb file BB_FILENAME and return a hash containing the following |
+# mapping: |
+# |
+# filename -> comma-separated list of pairs (function name=starting |
+# line number) to indicate the starting line of a function or |
+# =name to indicate an instrumented line |
+# |
+# for each entry in the .bb file. Filenames are absolute, i.e. relative |
+# filenames are prefixed with BASE_DIR. |
+# |
+# Die on error. |
+# |
+ |
+sub read_bb_file($$) |
+{ |
+ my $bb_filename = $_[0]; |
+ my $base_dir = $_[1]; |
+ my %result; |
+ my $filename; |
+ my $function_name; |
+ my $minus_one = sprintf("%d", 0x80000001); |
+ my $minus_two = sprintf("%d", 0x80000002); |
+ my $value; |
+ my $packed_word; |
+ local *INPUT; |
+ |
+ open(INPUT, $bb_filename) |
+ or die("ERROR: cannot read $bb_filename!\n"); |
+ |
+ binmode(INPUT); |
+ |
+ # Read data in words of 4 bytes |
+ while (read(INPUT, $packed_word, 4) == 4) |
+ { |
+ # Decode integer in intel byteorder |
+ $value = unpack_int32($packed_word, 0); |
+ |
+ # Note: the .bb file format is documented in GCC info pages |
+ if ($value == $minus_one) |
+ { |
+ # Filename follows |
+ $filename = read_string(*INPUT, $minus_one) |
+ or die("ERROR: incomplete filename in ". |
+ "$bb_filename!\n"); |
+ |
+ # Make path absolute |
+ $filename = solve_relative_path($base_dir, $filename); |
+ |
+ # Insert into hash if not yet present. |
+ # This is necessary because functions declared as |
+ # "inline" are not listed as actual functions in |
+ # .bb files |
+ if (!$result{$filename}) |
+ { |
+ $result{$filename}=""; |
+ } |
+ } |
+ elsif ($value == $minus_two) |
+ { |
+ # Function name follows |
+ $function_name = read_string(*INPUT, $minus_two) |
+ or die("ERROR: incomplete function ". |
+ "name in $bb_filename!\n"); |
+ $function_name =~ s/\W/_/g; |
+ } |
+ elsif ($value > 0) |
+ { |
+ if (defined($filename)) |
+ { |
+ $result{$filename} .= |
+ ($result{$filename} ? "," : ""). |
+ "=$value"; |
+ } |
+ else |
+ { |
+ warn("WARNING: unassigned line". |
+ " number in .bb file ". |
+ "$bb_filename\n"); |
+ } |
+ if ($function_name) |
+ { |
+ # Got a full entry filename, funcname, lineno |
+ # Add to resulting hash |
+ |
+ $result{$filename}.= |
+ ($result{$filename} ? "," : ""). |
+ join("=",($function_name,$value)); |
+ undef($function_name); |
+ } |
+ } |
+ } |
+ close(INPUT); |
+ |
+ if (!scalar(keys(%result))) |
+ { |
+ die("ERROR: no data found in $bb_filename!\n"); |
+ } |
+ return %result; |
+} |
+ |
+ |
+# |
+# read_string(handle, delimiter); |
+# |
+# Read and return a string in 4-byte chunks from HANDLE until DELIMITER |
+# is found. |
+# |
+# Return empty string on error. |
+# |
+ |
+sub read_string(*$) |
+{ |
+ my $HANDLE = $_[0]; |
+ my $delimiter = $_[1]; |
+ my $string = ""; |
+ my $packed_word; |
+ my $value; |
+ |
+ while (read($HANDLE,$packed_word,4) == 4) |
+ { |
+ $value = unpack_int32($packed_word, 0); |
+ |
+ if ($value == $delimiter) |
+ { |
+ # Remove trailing nil bytes |
+ $/="\0"; |
+ while (chomp($string)) {}; |
+ $/="\n"; |
+ return($string); |
+ } |
+ |
+ $string = $string.$packed_word; |
+ } |
+ return(""); |
+} |
+ |
+ |
+# |
+# read_gcno_file(bb_filename, base_dir) |
+# |
+# Read .gcno file BB_FILENAME and return a hash containing the following |
+# mapping: |
+# |
+# filename -> comma-separated list of pairs (function name=starting |
+# line number) to indicate the starting line of a function or |
+# =name to indicate an instrumented line |
+# |
+# for each entry in the .gcno file. Filenames are absolute, i.e. relative |
+# filenames are prefixed with BASE_DIR. |
+# |
+# Die on error. |
+# |
+ |
+sub read_gcno_file($$) |
+{ |
+ my $gcno_filename = $_[0]; |
+ my $base_dir = $_[1]; |
+ my %result; |
+ my $filename; |
+ my $function_name; |
+ my $lineno; |
+ my $length; |
+ my $value; |
+ my $endianness; |
+ my $blocks; |
+ my $packed_word; |
+ my $string; |
+ local *INPUT; |
+ |
+ open(INPUT, $gcno_filename) |
+ or die("ERROR: cannot read $gcno_filename!\n"); |
+ |
+ binmode(INPUT); |
+ |
+ read(INPUT, $packed_word, 4) == 4 |
+ or die("ERROR: Invalid gcno file format\n"); |
+ |
+ $value = unpack_int32($packed_word, 0); |
+ $endianness = !($value == $GCNO_FILE_MAGIC); |
+ |
+ unpack_int32($packed_word, $endianness) == $GCNO_FILE_MAGIC |
+ or die("ERROR: gcno file magic does not match\n"); |
+ |
+ seek(INPUT, 8, 1); |
+ |
+ # Read data in words of 4 bytes |
+ while (read(INPUT, $packed_word, 4) == 4) |
+ { |
+ # Decode integer in intel byteorder |
+ $value = unpack_int32($packed_word, $endianness); |
+ |
+ if ($value == $GCNO_FUNCTION_TAG) |
+ { |
+ # skip length, ident and checksum |
+ seek(INPUT, 12, 1); |
+ (undef, $function_name) = |
+ read_gcno_string(*INPUT, $endianness); |
+ $function_name =~ s/\W/_/g; |
+ (undef, $filename) = |
+ read_gcno_string(*INPUT, $endianness); |
+ $filename = solve_relative_path($base_dir, $filename); |
+ |
+ read(INPUT, $packed_word, 4); |
+ $lineno = unpack_int32($packed_word, $endianness); |
+ |
+ $result{$filename}.= |
+ ($result{$filename} ? "," : ""). |
+ join("=",($function_name,$lineno)); |
+ } |
+ elsif ($value == $GCNO_LINES_TAG) |
+ { |
+ # Check for names of files containing inlined code |
+ # included in this file |
+ read(INPUT, $packed_word, 4); |
+ $length = unpack_int32($packed_word, $endianness); |
+ if ($length > 0) |
+ { |
+ # Block number |
+ read(INPUT, $packed_word, 4); |
+ $length--; |
+ } |
+ while ($length > 0) |
+ { |
+ read(INPUT, $packed_word, 4); |
+ $lineno = unpack_int32($packed_word, |
+ $endianness); |
+ $length--; |
+ if ($lineno != 0) |
+ { |
+ if (defined($filename)) |
+ { |
+ $result{$filename} .= |
+ ($result{$filename} ? "," : ""). |
+ "=$lineno"; |
+ } |
+ else |
+ { |
+ warn("WARNING: unassigned line". |
+ " number in .gcno file ". |
+ "$gcno_filename\n"); |
+ } |
+ next; |
+ } |
+ last if ($length == 0); |
+ ($blocks, $string) = |
+ read_gcno_string(*INPUT, $endianness); |
+ if (defined($string)) |
+ { |
+ $filename = $string; |
+ } |
+ if ($blocks > 1) |
+ { |
+ $filename = solve_relative_path( |
+ $base_dir, $filename); |
+ if (!defined($result{$filename})) |
+ { |
+ $result{$filename} = ""; |
+ } |
+ } |
+ $length -= $blocks; |
+ } |
+ } |
+ else |
+ { |
+ read(INPUT, $packed_word, 4); |
+ $length = unpack_int32($packed_word, $endianness); |
+ seek(INPUT, 4 * $length, 1); |
+ } |
+ } |
+ close(INPUT); |
+ |
+ if (!scalar(keys(%result))) |
+ { |
+ die("ERROR: no data found in $gcno_filename!\n"); |
+ } |
+ return %result; |
+} |
+ |
+ |
+# |
+# read_gcno_string(handle, endianness); |
+# |
+# Read a string in 4-byte chunks from HANDLE. |
+# |
+# Return (number of 4-byte chunks read, string). |
+# |
+ |
+sub read_gcno_string(*$) |
+{ |
+ my $handle = $_[0]; |
+ my $endianness = $_[1]; |
+ my $number_of_blocks = 0; |
+ my $string = ""; |
+ my $packed_word; |
+ |
+ read($handle, $packed_word, 4) == 4 |
+ or die("ERROR: reading string\n"); |
+ |
+ $number_of_blocks = unpack_int32($packed_word, $endianness); |
+ |
+ if ($number_of_blocks == 0) |
+ { |
+ return (1, undef); |
+ } |
+ |
+ if (read($handle, $packed_word, 4 * $number_of_blocks) != |
+ 4 * $number_of_blocks) |
+ { |
+ my $msg = "invalid string size ".(4 * $number_of_blocks)." in ". |
+ "gcno file at position ".tell($handle)."\n"; |
+ if ($ignore[$ERROR_SOURCE]) |
+ { |
+ warn("WARNING: $msg"); |
+ return (1, undef); |
+ } |
+ else |
+ { |
+ die("ERROR: $msg"); |
+ } |
+ } |
+ |
+ $string = $string . $packed_word; |
+ |
+ # Remove trailing nil bytes |
+ $/="\0"; |
+ while (chomp($string)) {}; |
+ $/="\n"; |
+ |
+ return(1 + $number_of_blocks, $string); |
+} |
+ |
+ |
+# |
+# read_hammer_bbg_file(bb_filename, base_dir) |
+# |
+# Read .bbg file BB_FILENAME and return a hash containing the following |
+# mapping: |
+# |
+# filename -> comma-separated list of pairs (function name=starting |
+# line number) to indicate the starting line of a function or |
+# =name to indicate an instrumented line |
+# |
+# for each entry in the .bbg file. Filenames are absolute, i.e. relative |
+# filenames are prefixed with BASE_DIR. |
+# |
+# Die on error. |
+# |
+ |
+sub read_hammer_bbg_file($$) |
+{ |
+ my $bbg_filename = $_[0]; |
+ my $base_dir = $_[1]; |
+ my %result; |
+ my $filename; |
+ my $function_name; |
+ my $first_line; |
+ my $lineno; |
+ my $length; |
+ my $value; |
+ my $endianness; |
+ my $blocks; |
+ my $packed_word; |
+ local *INPUT; |
+ |
+ open(INPUT, $bbg_filename) |
+ or die("ERROR: cannot read $bbg_filename!\n"); |
+ |
+ binmode(INPUT); |
+ |
+ # Read magic |
+ read(INPUT, $packed_word, 4) == 4 |
+ or die("ERROR: invalid bbg file format\n"); |
+ |
+ $endianness = 1; |
+ |
+ unpack_int32($packed_word, $endianness) == $BBG_FILE_MAGIC |
+ or die("ERROR: bbg file magic does not match\n"); |
+ |
+ # Skip version |
+ seek(INPUT, 4, 1); |
+ |
+ # Read data in words of 4 bytes |
+ while (read(INPUT, $packed_word, 4) == 4) |
+ { |
+ # Get record tag |
+ $value = unpack_int32($packed_word, $endianness); |
+ |
+ # Get record length |
+ read(INPUT, $packed_word, 4); |
+ $length = unpack_int32($packed_word, $endianness); |
+ |
+ if ($value == $GCNO_FUNCTION_TAG) |
+ { |
+ # Get function name |
+ ($value, $function_name) = |
+ read_hammer_bbg_string(*INPUT, $endianness); |
+ $function_name =~ s/\W/_/g; |
+ $filename = undef; |
+ $first_line = undef; |
+ |
+ seek(INPUT, $length - $value * 4, 1); |
+ } |
+ elsif ($value == $GCNO_LINES_TAG) |
+ { |
+ # Get linenumber and filename |
+ # Skip block number |
+ seek(INPUT, 4, 1); |
+ $length -= 4; |
+ |
+ while ($length > 0) |
+ { |
+ read(INPUT, $packed_word, 4); |
+ $lineno = unpack_int32($packed_word, |
+ $endianness); |
+ $length -= 4; |
+ if ($lineno != 0) |
+ { |
+ if (!defined($first_line)) |
+ { |
+ $first_line = $lineno; |
+ } |
+ if (defined($filename)) |
+ { |
+ $result{$filename} .= |
+ ($result{$filename} ? "," : ""). |
+ "=$lineno"; |
+ } |
+ else |
+ { |
+ warn("WARNING: unassigned line". |
+ " number in .bbg file ". |
+ "$bbg_filename\n"); |
+ } |
+ next; |
+ } |
+ ($blocks, $value) = |
+ read_hammer_bbg_string( |
+ *INPUT, $endianness); |
+ # Add all filenames to result list |
+ if (defined($value)) |
+ { |
+ $value = solve_relative_path( |
+ $base_dir, $value); |
+ if (!defined($result{$value})) |
+ { |
+ $result{$value} = undef; |
+ } |
+ if (!defined($filename)) |
+ { |
+ $filename = $value; |
+ } |
+ } |
+ $length -= $blocks * 4; |
+ |
+ # Got a complete data set? |
+ if (defined($filename) && |
+ defined($first_line) && |
+ defined($function_name)) |
+ { |
+ # Add it to our result hash |
+ if (defined($result{$filename})) |
+ { |
+ $result{$filename} .= |
+ ",$function_name=$first_line"; |
+ } |
+ else |
+ { |
+ $result{$filename} = |
+ "$function_name=$first_line"; |
+ } |
+ $function_name = undef; |
+ $filename = undef; |
+ $first_line = undef; |
+ } |
+ } |
+ } |
+ else |
+ { |
+ # Skip other records |
+ seek(INPUT, $length, 1); |
+ } |
+ } |
+ close(INPUT); |
+ |
+ if (!scalar(keys(%result))) |
+ { |
+ die("ERROR: no data found in $bbg_filename!\n"); |
+ } |
+ return %result; |
+} |
+ |
+ |
+# |
+# read_hammer_bbg_string(handle, endianness); |
+# |
+# Read a string in 4-byte chunks from HANDLE. |
+# |
+# Return (number of 4-byte chunks read, string). |
+# |
+ |
+sub read_hammer_bbg_string(*$) |
+{ |
+ my $handle = $_[0]; |
+ my $endianness = $_[1]; |
+ my $length = 0; |
+ my $string = ""; |
+ my $packed_word; |
+ my $pad; |
+ |
+ read($handle, $packed_word, 4) == 4 |
+ or die("ERROR: reading string\n"); |
+ |
+ $length = unpack_int32($packed_word, $endianness); |
+ $pad = 4 - $length % 4; |
+ |
+ if ($length == 0) |
+ { |
+ return (1, undef); |
+ } |
+ |
+ read($handle, $string, $length) == |
+ $length or die("ERROR: reading string\n"); |
+ seek($handle, $pad, 1); |
+ |
+ return(1 + ($length + $pad) / 4, $string); |
+} |
+ |
+# |
+# unpack_int32(word, endianness) |
+# |
+# Interpret 4-byte binary string WORD as signed 32 bit integer in |
+# endian encoding defined by ENDIANNESS (0=little, 1=big) and return its |
+# value. |
+# |
+ |
+sub unpack_int32($$) |
+{ |
+ return sprintf("%d", unpack($_[1] ? "N" : "V",$_[0])); |
+} |
+ |
+ |
+# |
+# Get the GCOV tool version. Return an integer number which represents the |
+# GCOV version. Version numbers can be compared using standard integer |
+# operations. |
+# |
+ |
+sub get_gcov_version() |
+{ |
+ local *HANDLE; |
+ my $version_string; |
+ my $result; |
+ |
+ open(GCOV_PIPE, "$gcov_tool -v |") |
+ or die("ERROR: cannot retrieve gcov version!\n"); |
+ $version_string = <GCOV_PIPE>; |
+ close(GCOV_PIPE); |
+ |
+ $result = 0; |
+ if ($version_string =~ /(\d+)\.(\d+)(\.(\d+))?/) |
+ { |
+ if (defined($4)) |
+ { |
+ info("Found gcov version: $1.$2.$4\n"); |
+ $result = $1 << 16 | $2 << 8 | $4; |
+ } |
+ else |
+ { |
+ info("Found gcov version: $1.$2\n"); |
+ $result = $1 << 16 | $2 << 8; |
+ } |
+ } |
+ if ($version_string =~ /suse/i && $result == 0x30303 || |
+ $version_string =~ /mandrake/i && $result == 0x30302) |
+ { |
+ info("Using compatibility mode for GCC 3.3 (hammer)\n"); |
+ $compatibility = $COMPAT_HAMMER; |
+ } |
+ return $result; |
+} |
+ |
+ |
+# |
+# info(printf_parameter) |
+# |
+# Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag |
+# is not set. |
+# |
+ |
+sub info(@) |
+{ |
+ if (!$quiet) |
+ { |
+ # Print info string |
+ if (defined($output_filename) && ($output_filename eq "-")) |
+ { |
+ # Don't interfere with the .info output to STDOUT |
+ printf(STDERR @_); |
+ } |
+ else |
+ { |
+ printf(@_); |
+ } |
+ } |
+} |
+ |
+ |
+# |
+# int_handler() |
+# |
+# Called when the script was interrupted by an INT signal (e.g. CTRl-C) |
+# |
+ |
+sub int_handler() |
+{ |
+ if ($cwd) { chdir($cwd); } |
+ info("Aborted.\n"); |
+ exit(1); |
+} |
+ |
+ |
+# |
+# system_no_output(mode, parameters) |
+# |
+# Call an external program using PARAMETERS while suppressing depending on |
+# the value of MODE: |
+# |
+# MODE & 1: suppress STDOUT |
+# MODE & 2: suppress STDERR |
+# |
+# Return 0 on success, non-zero otherwise. |
+# |
+ |
+sub system_no_output($@) |
+{ |
+ my $mode = shift; |
+ my $result; |
+ local *OLD_STDERR; |
+ local *OLD_STDOUT; |
+ |
+ # Save old stdout and stderr handles |
+ ($mode & 1) && open(OLD_STDOUT, ">>&STDOUT"); |
+ ($mode & 2) && open(OLD_STDERR, ">>&STDERR"); |
+ |
+ # Redirect to /dev/null |
+ ($mode & 1) && open(STDOUT, ">/dev/null"); |
+ ($mode & 2) && open(STDERR, ">/dev/null"); |
+ |
+ system(@_); |
+ $result = $?; |
+ |
+ # Close redirected handles |
+ ($mode & 1) && close(STDOUT); |
+ ($mode & 2) && close(STDERR); |
+ |
+ # Restore old handles |
+ ($mode & 1) && open(STDOUT, ">>&OLD_STDOUT"); |
+ ($mode & 2) && open(STDERR, ">>&OLD_STDERR"); |
+ |
+ return $result; |
+} |
+ |
+ |
+# |
+# read_config(filename) |
+# |
+# Read configuration file FILENAME and return a reference to a hash containing |
+# all valid key=value pairs found. |
+# |
+ |
+sub read_config($) |
+{ |
+ my $filename = $_[0]; |
+ my %result; |
+ my $key; |
+ my $value; |
+ local *HANDLE; |
+ |
+ if (!open(HANDLE, "<$filename")) |
+ { |
+ warn("WARNING: cannot read configuration file $filename\n"); |
+ return undef; |
+ } |
+ while (<HANDLE>) |
+ { |
+ chomp; |
+ # Skip comments |
+ s/#.*//; |
+ # Remove leading blanks |
+ s/^\s+//; |
+ # Remove trailing blanks |
+ s/\s+$//; |
+ next unless length; |
+ ($key, $value) = split(/\s*=\s*/, $_, 2); |
+ if (defined($key) && defined($value)) |
+ { |
+ $result{$key} = $value; |
+ } |
+ else |
+ { |
+ warn("WARNING: malformed statement in line $. ". |
+ "of configuration file $filename\n"); |
+ } |
+ } |
+ close(HANDLE); |
+ return \%result; |
+} |
+ |
+ |
+# |
+# apply_config(REF) |
+# |
+# REF is a reference to a hash containing the following mapping: |
+# |
+# key_string => var_ref |
+# |
+# where KEY_STRING is a keyword and VAR_REF is a reference to an associated |
+# variable. If the global configuration hash CONFIG contains a value for |
+# keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. |
+# |
+ |
+sub apply_config($) |
+{ |
+ my $ref = $_[0]; |
+ |
+ foreach (keys(%{$ref})) |
+ { |
+ if (defined($config->{$_})) |
+ { |
+ ${$ref->{$_}} = $config->{$_}; |
+ } |
+ } |
+} |
+ |
+ |
+sub gen_initial_info($) |
+{ |
+ my $directory = $_[0]; |
+ my @file_list; |
+ |
+ if (-d $directory) |
+ { |
+ info("Scanning $directory for $graph_file_extension ". |
+ "files ...\n"); |
+ |
+ @file_list = `find "$directory" $maxdepth $follow -name \\*$graph_file_extension -type f 2>/dev/null`; |
+ chomp(@file_list); |
+ @file_list or die("ERROR: no $graph_file_extension files ". |
+ "found in $directory!\n"); |
+ info("Found %d graph files in %s\n", $#file_list+1, $directory); |
+ } |
+ else |
+ { |
+ @file_list = ($directory); |
+ } |
+ |
+ # Process all files in list |
+ foreach (@file_list) { process_graphfile($_); } |
+} |
+ |
+sub process_graphfile($) |
+{ |
+ my $graph_filename = $_[0]; |
+ my $graph_dir; |
+ my $graph_basename; |
+ my $source_dir; |
+ my $base_dir; |
+ my %graph_data; |
+ my $filename; |
+ local *INFO_HANDLE; |
+ |
+ info("Processing $_[0]\n"); |
+ |
+ # Get path to data file in absolute and normalized form (begins with /, |
+ # contains no more ../ or ./) |
+ $graph_filename = solve_relative_path($cwd, $graph_filename); |
+ |
+ # Get directory and basename of data file |
+ ($graph_dir, $graph_basename) = split_filename($graph_filename); |
+ |
+ # avoid files from .libs dirs |
+ if ($compat_libtool && $graph_dir =~ m/(.*)\/\.libs$/) { |
+ $source_dir = $1; |
+ } else { |
+ $source_dir = $graph_dir; |
+ } |
+ |
+ # Construct base_dir for current file |
+ if ($base_directory) |
+ { |
+ $base_dir = $base_directory; |
+ } |
+ else |
+ { |
+ $base_dir = $source_dir; |
+ } |
+ |
+ if ($gcov_version < $GCOV_VERSION_3_4_0) |
+ { |
+ if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) |
+ { |
+ %graph_data = read_hammer_bbg_file($graph_filename, |
+ $base_dir); |
+ } |
+ else |
+ { |
+ %graph_data = read_bb_file($graph_filename, $base_dir); |
+ } |
+ } |
+ else |
+ { |
+ %graph_data = read_gcno_file($graph_filename, $base_dir); |
+ } |
+ |
+ # Check whether we're writing to a single file |
+ if ($output_filename) |
+ { |
+ if ($output_filename eq "-") |
+ { |
+ *INFO_HANDLE = *STDOUT; |
+ } |
+ else |
+ { |
+ # Append to output file |
+ open(INFO_HANDLE, ">>$output_filename") |
+ or die("ERROR: cannot write to ". |
+ "$output_filename!\n"); |
+ } |
+ } |
+ else |
+ { |
+ # Open .info file for output |
+ open(INFO_HANDLE, ">$graph_filename.info") |
+ or die("ERROR: cannot create $graph_filename.info!\n"); |
+ } |
+ |
+ # Write test name |
+ printf(INFO_HANDLE "TN:%s\n", $test_name); |
+ foreach $filename (keys(%graph_data)) |
+ { |
+ my %lines; |
+ my $count = 0; |
+ my @functions; |
+ |
+ print(INFO_HANDLE "SF:$filename\n"); |
+ |
+ # Write function related data |
+ foreach (split(",",$graph_data{$filename})) |
+ { |
+ my ($fn, $line) = split("=", $_); |
+ |
+ if ($fn eq "") |
+ { |
+ $lines{$line} = ""; |
+ next; |
+ } |
+ |
+ # Normalize function name |
+ $fn =~ s/\W/_/g; |
+ |
+ print(INFO_HANDLE "FN:$line,$fn\n"); |
+ push(@functions, $fn); |
+ } |
+ foreach (@functions) { |
+ print(INFO_HANDLE "FNDA:$_,0\n"); |
+ } |
+ print(INFO_HANDLE "FNF:".scalar(@functions)."\n"); |
+ print(INFO_HANDLE "FNH:0\n"); |
+ |
+ # Write line related data |
+ foreach (sort {$a <=> $b } keys(%lines)) |
+ { |
+ print(INFO_HANDLE "DA:$_,0\n"); |
+ $count++; |
+ } |
+ print(INFO_HANDLE "LH:0\n"); |
+ print(INFO_HANDLE "LF:$count\n"); |
+ print(INFO_HANDLE "end_of_record\n"); |
+ } |
+ if (!($output_filename && ($output_filename eq "-"))) |
+ { |
+ close(INFO_HANDLE); |
+ } |
+} |
+ |
+sub warn_handler($) |
+{ |
+ my ($msg) = @_; |
+ |
+ warn("$tool_name: $msg"); |
+} |
+ |
+sub die_handler($) |
+{ |
+ my ($msg) = @_; |
+ |
+ die("$tool_name: $msg"); |
+} |
Property changes on: third_party/lcov/bin/geninfo |
___________________________________________________________________ |
Name: svn:executable |
+ * |