| 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
|
| + *
|
|
|
|
|