| Index: third_party/lcov-1.9/bin/geninfo
|
| diff --git a/third_party/lcov-1.9/bin/geninfo b/third_party/lcov-1.9/bin/geninfo
|
| deleted file mode 100755
|
| index dcb1a6781863ac3233a42c60fa038e6560415c61..0000000000000000000000000000000000000000
|
| --- a/third_party/lcov-1.9/bin/geninfo
|
| +++ /dev/null
|
| @@ -1,3068 +0,0 @@
|
| -#!/usr/bin/perl -w
|
| -#
|
| -# Copyright (c) International Business Machines Corp., 2002,2010
|
| -#
|
| -# 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 File::Spec::Functions qw /abs2rel catdir file_name_is_absolute splitdir
|
| - splitpath/;
|
| -use Getopt::Long;
|
| -use Digest::MD5 qw(md5_base64);
|
| -
|
| -
|
| -# Constants
|
| -our $lcov_version = 'LCOV version 1.9';
|
| -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;
|
| -our $ERROR_GRAPH = 2;
|
| -
|
| -our $EXCL_START = "LCOV_EXCL_START";
|
| -our $EXCL_STOP = "LCOV_EXCL_STOP";
|
| -our $EXCL_LINE = "LCOV_EXCL_LINE";
|
| -
|
| -our $BR_LINE = 0;
|
| -our $BR_BLOCK = 1;
|
| -our $BR_BRANCH = 2;
|
| -our $BR_TAKEN = 3;
|
| -our $BR_VEC_ENTRIES = 4;
|
| -our $BR_VEC_WIDTH = 32;
|
| -
|
| -our $UNNAMED_BLOCK = 9999;
|
| -
|
| -# 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 read_gcov_header($);
|
| -sub read_gcov_file($);
|
| -sub info(@);
|
| -sub get_gcov_version();
|
| -sub system_no_output($@);
|
| -sub read_config($);
|
| -sub apply_config($);
|
| -sub get_exclusion_data($);
|
| -sub apply_exclusion_data($$);
|
| -sub process_graphfile($$);
|
| -sub filter_fn_name($);
|
| -sub warn_handler($);
|
| -sub die_handler($);
|
| -sub graph_error($$);
|
| -sub graph_expect($);
|
| -sub graph_read(*$;$);
|
| -sub graph_skip(*$;$);
|
| -sub sort_uniq(@);
|
| -sub sort_uniq_lex(@);
|
| -sub graph_cleanup($);
|
| -sub graph_find_base($);
|
| -sub graph_from_bb($$$);
|
| -sub graph_add_order($$$);
|
| -sub read_bb_word(*;$);
|
| -sub read_bb_value(*;$);
|
| -sub read_bb_string(*$);
|
| -sub read_bb($$);
|
| -sub read_bbg_word(*;$);
|
| -sub read_bbg_value(*;$);
|
| -sub read_bbg_string(*);
|
| -sub read_bbg_lines_record(*$$$$$$);
|
| -sub read_bbg($$);
|
| -sub read_gcno_word(*;$);
|
| -sub read_gcno_value(*$;$);
|
| -sub read_gcno_string(*$);
|
| -sub read_gcno_lines_record(*$$$$$$$);
|
| -sub read_gcno_function_record(*$$$$);
|
| -sub read_gcno($$);
|
| -sub get_gcov_capabilities();
|
| -sub get_overall_line($$$$);
|
| -sub print_overall_rate($$$$$$$$$);
|
| -sub br_gvec_len($);
|
| -sub br_gvec_get($$);
|
| -sub debug($);
|
| -sub int_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 $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 $no_markers = 0;
|
| -our $opt_derive_func_data = 0;
|
| -our $debug = 0;
|
| -our $gcov_caps;
|
| -our @gcov_options;
|
| -
|
| -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;
|
| -
|
| -# Prettify version string
|
| -$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/;
|
| -
|
| -# Set LANG so that gcov output will be in a unified format
|
| -$ENV{"LANG"} = "C";
|
| -
|
| -# Read configuration file if available
|
| -if (defined($ENV{"HOME"}) && (-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|t=s" => \$test_name,
|
| - "output-filename|o=s" => \$output_filename,
|
| - "checksum" => \$checksum,
|
| - "no-checksum" => \$no_checksum,
|
| - "base-directory|b=s" => \$base_directory,
|
| - "version|v" =>\$version,
|
| - "quiet|q" => \$quiet,
|
| - "help|h|?" => \$help,
|
| - "follow|f" => \$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,
|
| - "no-markers" => \$no_markers,
|
| - "derive-func-data" => \$opt_derive_func_data,
|
| - "debug" => \$debug,
|
| - ))
|
| -{
|
| - 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; };
|
| - /^graph$/ && do { $ignore[$ERROR_GRAPH] = 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";
|
| -}
|
| -
|
| -# Determine gcov options
|
| -$gcov_caps = get_gcov_capabilities();
|
| -push(@gcov_options, "-b") if ($gcov_caps->{'branch-probabilities'});
|
| -push(@gcov_options, "-c") if ($gcov_caps->{'branch-counts'});
|
| -push(@gcov_options, "-a") if ($gcov_caps->{'all-blocks'});
|
| -push(@gcov_options, "-p") if ($gcov_caps->{'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
|
| -foreach my $entry (@data_directory) {
|
| - gen_info($entry);
|
| -}
|
| -
|
| -if ($initial) {
|
| - warn("Note: --initial does not generate branch coverage ".
|
| - "data\n");
|
| -}
|
| -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, graph)
|
| - --no-recursion Exclude subdirectories from processing
|
| - --function-coverage Capture function call counts
|
| - --no-markers Ignore exclusion markers in source code
|
| - --derive-func-data Generate function data from line data
|
| -
|
| -For more information see: $lcov_url
|
| -END_OF_USAGE
|
| - ;
|
| -}
|
| -
|
| -#
|
| -# get_common_prefix(min_dir, filenames)
|
| -#
|
| -# Return the longest path prefix shared by all filenames. MIN_DIR specifies
|
| -# the minimum number of directories that a filename may have after removing
|
| -# the prefix.
|
| -#
|
| -
|
| -sub get_common_prefix($@)
|
| -{
|
| - my ($min_dir, @files) = @_;
|
| - my $file;
|
| - my @prefix;
|
| - my $i;
|
| -
|
| - foreach $file (@files) {
|
| - my ($v, $d, $f) = splitpath($file);
|
| - my @comp = splitdir($d);
|
| -
|
| - if (!@prefix) {
|
| - @prefix = @comp;
|
| - next;
|
| - }
|
| - for ($i = 0; $i < scalar(@comp) && $i < scalar(@prefix); $i++) {
|
| - if ($comp[$i] ne $prefix[$i] ||
|
| - ((scalar(@comp) - ($i + 1)) <= $min_dir)) {
|
| - delete(@prefix[$i..scalar(@prefix)]);
|
| - last;
|
| - }
|
| - }
|
| - }
|
| -
|
| - return catdir(@prefix);
|
| -}
|
| -
|
| -#
|
| -# 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;
|
| - my $file;
|
| - my $prefix;
|
| - my $type;
|
| - my $ext;
|
| -
|
| - if ($initial) {
|
| - $type = "graph";
|
| - $ext = $graph_file_extension;
|
| - } else {
|
| - $type = "data";
|
| - $ext = $data_file_extension;
|
| - }
|
| -
|
| - if (-d $directory)
|
| - {
|
| - info("Scanning $directory for $ext files ...\n");
|
| -
|
| - @file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f 2>/dev/null`;
|
| - chomp(@file_list);
|
| - @file_list or
|
| - die("ERROR: no $ext files found in $directory!\n");
|
| - $prefix = get_common_prefix(1, @file_list);
|
| - info("Found %d %s files in %s\n", $#file_list+1, $type,
|
| - $directory);
|
| - }
|
| - else
|
| - {
|
| - @file_list = ($directory);
|
| - $prefix = "";
|
| - }
|
| -
|
| - # Process all files in list
|
| - foreach $file (@file_list) {
|
| - # Process file
|
| - if ($initial) {
|
| - process_graphfile($file, $prefix);
|
| - } else {
|
| - process_dafile($file, $prefix);
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -sub derive_data($$$)
|
| -{
|
| - my ($contentdata, $funcdata, $bbdata) = @_;
|
| - my @gcov_content = @{$contentdata};
|
| - my @gcov_functions = @{$funcdata};
|
| - my %fn_count;
|
| - my %ln_fn;
|
| - my $line;
|
| - my $maxline;
|
| - my %fn_name;
|
| - my $fn;
|
| - my $count;
|
| -
|
| - if (!defined($bbdata)) {
|
| - return @gcov_functions;
|
| - }
|
| -
|
| - # First add existing function data
|
| - while (@gcov_functions) {
|
| - $count = shift(@gcov_functions);
|
| - $fn = shift(@gcov_functions);
|
| -
|
| - $fn_count{$fn} = $count;
|
| - }
|
| -
|
| - # Convert line coverage data to function data
|
| - foreach $fn (keys(%{$bbdata})) {
|
| - my $line_data = $bbdata->{$fn};
|
| - my $line;
|
| -
|
| - if ($fn eq "") {
|
| - next;
|
| - }
|
| - # Find the lowest line count for this function
|
| - $count = 0;
|
| - foreach $line (@$line_data) {
|
| - my $lcount = $gcov_content[ ( $line - 1 ) * 3 + 1 ];
|
| -
|
| - if (($lcount > 0) &&
|
| - (($count == 0) || ($lcount < $count))) {
|
| - $count = $lcount;
|
| - }
|
| - }
|
| - $fn_count{$fn} = $count;
|
| - }
|
| -
|
| -
|
| - # Check if we got data for all functions
|
| - foreach $fn (keys(%fn_name)) {
|
| - if ($fn eq "") {
|
| - next;
|
| - }
|
| - if (defined($fn_count{$fn})) {
|
| - next;
|
| - }
|
| - warn("WARNING: no derived data found for function $fn\n");
|
| - }
|
| -
|
| - # Convert hash to list in @gcov_functions format
|
| - foreach $fn (sort(keys(%fn_count))) {
|
| - push(@gcov_functions, $fn_count{$fn}, $fn);
|
| - }
|
| -
|
| - return @gcov_functions;
|
| -}
|
| -
|
| -#
|
| -# get_filenames(directory, pattern)
|
| -#
|
| -# Return a list of filenames found in directory which match the specified
|
| -# pattern.
|
| -#
|
| -# Die on error.
|
| -#
|
| -
|
| -sub get_filenames($$)
|
| -{
|
| - my ($dirname, $pattern) = @_;
|
| - my @result;
|
| - my $directory;
|
| - local *DIR;
|
| -
|
| - opendir(DIR, $dirname) or
|
| - die("ERROR: cannot read directory $dirname\n");
|
| - while ($directory = readdir(DIR)) {
|
| - push(@result, $directory) if ($directory =~ /$pattern/);
|
| - }
|
| - closedir(DIR);
|
| -
|
| - return @result;
|
| -}
|
| -
|
| -#
|
| -# process_dafile(da_filename, dir)
|
| -#
|
| -# Create a .info file for a single data file.
|
| -#
|
| -# Die on error.
|
| -#
|
| -
|
| -sub process_dafile($$)
|
| -{
|
| - my ($file, $dir) = @_;
|
| - 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_basename; # Basename of the original graph file
|
| - my $graph; # Contents of graph file
|
| - my $instr; # Contents of graph file part 2
|
| - 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 $br_hit;
|
| - my $br_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 @tmp_links; # Temporary links to be cleaned up
|
| - my @result;
|
| - my $index;
|
| - my $da_renamed; # If data file is to be renamed
|
| - local *INFO_HANDLE;
|
| -
|
| - info("Processing %s\n", abs2rel($file, $dir));
|
| - # Get path to data file in absolute and normalized form (begins with /,
|
| - # contains no more ../ or ./)
|
| - $da_filename = solve_relative_path($cwd, $file);
|
| -
|
| - # 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_basename = $da_basename.$graph_file_extension;
|
| - $bb_filename = "$da_dir/$bb_basename";
|
| -
|
| - # 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)
|
| - {
|
| - ($instr, $graph) = read_bbg($bb_filename, $base_dir);
|
| - }
|
| - else
|
| - {
|
| - ($instr, $graph) = read_bb($bb_filename, $base_dir);
|
| - }
|
| - }
|
| - else
|
| - {
|
| - ($instr, $graph) = read_gcno($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");
|
| - push(@tmp_links,
|
| - "$object_dir/$da_basename$data_file_extension");
|
| - # Need to create link to graph file if basename of link
|
| - # and file are different (CONFIG_MODVERSION compat)
|
| - if ((basename($bb_filename) ne $bb_basename) &&
|
| - (! -e "$object_dir/$bb_basename")) {
|
| - symlink($bb_filename, "$object_dir/$bb_basename") or
|
| - warn("WARNING: cannot create link ".
|
| - "$object_dir/$bb_basename\n");
|
| - push(@tmp_links, "$object_dir/$bb_basename");
|
| - }
|
| - }
|
| -
|
| - # 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
|
| - $gcov_error = system_no_output(1, $gcov_tool, $da_filename,
|
| - "-o", $object_dir, @gcov_options);
|
| -
|
| - if ($da_renamed)
|
| - {
|
| - system_no_output(3, "mv", "$da_filename.ori", "$da_filename")
|
| - and die ("ERROR: cannot rename $da_filename.ori");
|
| - }
|
| -
|
| - # Clean up temporary links
|
| - foreach (@tmp_links) {
|
| - unlink($_);
|
| - }
|
| -
|
| - 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 = get_filenames('.', '\.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(%{$instr});
|
| - foreach $gcov_file (sort(@gcov_list))
|
| - {
|
| - my $i;
|
| - my $num;
|
| -
|
| - ($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(%{$instr}));
|
| -
|
| - # 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);
|
| - if (!defined($result[0])) {
|
| - warn("WARNING: skipping unreadable file ".
|
| - $gcov_file."\n");
|
| - unlink($gcov_file);
|
| - next;
|
| - }
|
| - @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);
|
| -
|
| - # If requested, derive function coverage data from
|
| - # line coverage data of the first line of a function
|
| - if ($opt_derive_func_data) {
|
| - @gcov_functions =
|
| - derive_data(\@gcov_content, \@gcov_functions,
|
| - $graph->{$source_filename});
|
| - }
|
| -
|
| - # Write function-related information
|
| - if (defined($graph->{$source_filename}))
|
| - {
|
| - my $fn_data = $graph->{$source_filename};
|
| - my $fn;
|
| -
|
| - foreach $fn (sort
|
| - {$fn_data->{$a}->[0] <=> $fn_data->{$b}->[0]}
|
| - keys(%{$fn_data})) {
|
| - my $ln_data = $fn_data->{$fn};
|
| - my $line = $ln_data->[0];
|
| -
|
| - # Skip empty function
|
| - if ($fn eq "") {
|
| - next;
|
| - }
|
| - # Remove excluded functions
|
| - if (!$no_markers) {
|
| - my $gfn;
|
| - my $found = 0;
|
| -
|
| - foreach $gfn (@gcov_functions) {
|
| - if ($gfn eq $fn) {
|
| - $found = 1;
|
| - last;
|
| - }
|
| - }
|
| - if (!$found) {
|
| - next;
|
| - }
|
| - }
|
| -
|
| - # Normalize function name
|
| - $fn = filter_fn_name($fn);
|
| -
|
| - 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)
|
| - {
|
| - my $count = shift(@gcov_functions);
|
| - my $fn = shift(@gcov_functions);
|
| -
|
| - $fn = filter_fn_name($fn);
|
| - printf(INFO_HANDLE "FNDA:$count,$fn\n");
|
| - $funcs_found++;
|
| - $funcs_hit++ if ($count > 0);
|
| - }
|
| - if ($funcs_found > 0) {
|
| - printf(INFO_HANDLE "FNF:%s\n", $funcs_found);
|
| - printf(INFO_HANDLE "FNH:%s\n", $funcs_hit);
|
| - }
|
| -
|
| - # Write coverage information for each instrumented branch:
|
| - #
|
| - # BRDA:<line number>,<block number>,<branch number>,<taken>
|
| - #
|
| - # where 'taken' is the number of times the branch was taken
|
| - # or '-' if the block to which the branch belongs was never
|
| - # executed
|
| - $br_found = 0;
|
| - $br_hit = 0;
|
| - $num = br_gvec_len($gcov_branches);
|
| - for ($i = 0; $i < $num; $i++) {
|
| - my ($line, $block, $branch, $taken) =
|
| - br_gvec_get($gcov_branches, $i);
|
| -
|
| - print(INFO_HANDLE "BRDA:$line,$block,$branch,$taken\n");
|
| - $br_found++;
|
| - $br_hit++ if ($taken ne '-' && $taken > 0);
|
| - }
|
| - if ($br_found > 0) {
|
| - printf(INFO_HANDLE "BRF:%s\n", $br_found);
|
| - printf(INFO_HANDLE "BRH:%s\n", $br_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);
|
| - }
|
| -
|
| - # 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, @list) = @_;
|
| - my ($vol, $dir, $file) = splitpath($filename);
|
| - my @comp = splitdir($dir);
|
| - my $comps = scalar(@comp);
|
| - my $entry;
|
| - my @result;
|
| -
|
| -entry:
|
| - foreach $entry (@list) {
|
| - my ($evol, $edir, $efile) = splitpath($entry);
|
| - my @ecomp;
|
| - my $ecomps;
|
| - my $i;
|
| -
|
| - # Filename component must match
|
| - if ($efile ne $file) {
|
| - next;
|
| - }
|
| - # Check directory components last to first for match
|
| - @ecomp = splitdir($edir);
|
| - $ecomps = scalar(@ecomp);
|
| - if ($ecomps < $comps) {
|
| - next;
|
| - }
|
| - for ($i = 0; $i < $comps; $i++) {
|
| - if ($comp[$comps - $i - 1] ne
|
| - $ecomp[$ecomps - $i - 1]) {
|
| - next entry;
|
| - }
|
| - }
|
| - push(@result, $entry),
|
| - }
|
| -
|
| - 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;
|
| -
|
| - # Also remove CR from line-end
|
| - s/\015$//;
|
| -
|
| - 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);
|
| -}
|
| -
|
| -
|
| -#
|
| -# 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($_);
|
| -
|
| - # Also remove CR from line-end
|
| - s/\015$//;
|
| -
|
| - 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);
|
| -}
|
| -
|
| -
|
| -#
|
| -# br_gvec_len(vector)
|
| -#
|
| -# Return the number of entries in the branch coverage vector.
|
| -#
|
| -
|
| -sub br_gvec_len($)
|
| -{
|
| - my ($vec) = @_;
|
| -
|
| - return 0 if (!defined($vec));
|
| - return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES;
|
| -}
|
| -
|
| -
|
| -#
|
| -# br_gvec_get(vector, number)
|
| -#
|
| -# Return an entry from the branch coverage vector.
|
| -#
|
| -
|
| -sub br_gvec_get($$)
|
| -{
|
| - my ($vec, $num) = @_;
|
| - my $line;
|
| - my $block;
|
| - my $branch;
|
| - my $taken;
|
| - my $offset = $num * $BR_VEC_ENTRIES;
|
| -
|
| - # Retrieve data from vector
|
| - $line = vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH);
|
| - $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH);
|
| - $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH);
|
| - $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH);
|
| -
|
| - # Decode taken value from an integer
|
| - if ($taken == 0) {
|
| - $taken = "-";
|
| - } else {
|
| - $taken--;
|
| - }
|
| -
|
| - return ($line, $block, $branch, $taken);
|
| -}
|
| -
|
| -
|
| -#
|
| -# br_gvec_push(vector, line, block, branch, taken)
|
| -#
|
| -# Add an entry to the branch coverage vector.
|
| -#
|
| -
|
| -sub br_gvec_push($$$$$)
|
| -{
|
| - my ($vec, $line, $block, $branch, $taken) = @_;
|
| - my $offset;
|
| -
|
| - $vec = "" if (!defined($vec));
|
| - $offset = br_gvec_len($vec) * $BR_VEC_ENTRIES;
|
| -
|
| - # Encode taken value into an integer
|
| - if ($taken eq "-") {
|
| - $taken = 0;
|
| - } else {
|
| - $taken++;
|
| - }
|
| -
|
| - # Add to vector
|
| - vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH) = $line;
|
| - vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block;
|
| - vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch;
|
| - vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken;
|
| -
|
| - return $vec;
|
| -}
|
| -
|
| -
|
| -#
|
| -# 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 vector of 4 4-byte long elements for each branch:
|
| -# line number, block number, branch number, count + 1 or 0
|
| -#
|
| -# 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;
|
| - my $exclude_flag = 0;
|
| - my $exclude_line = 0;
|
| - my $last_block = $UNNAMED_BLOCK;
|
| - my $last_line = 0;
|
| - local *INPUT;
|
| -
|
| - if (!open(INPUT, $filename)) {
|
| - if ($ignore_errors[$ERROR_GCOV])
|
| - {
|
| - warn("WARNING: cannot read $filename!\n");
|
| - return (undef, undef, undef);
|
| - }
|
| - 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($_);
|
| -
|
| - # Also remove CR from line-end
|
| - s/\015$//;
|
| -
|
| - if (/^branch\s+(\d+)\s+taken\s+=\s+(\d+)/) {
|
| - next if ($exclude_line);
|
| - $branches = br_gvec_push($branches, $last_line,
|
| - $last_block, $1, $2);
|
| - } elsif (/^branch\s+(\d+)\s+never\s+executed/) {
|
| - next if ($exclude_line);
|
| - $branches = br_gvec_push($branches, $last_line,
|
| - $last_block, $1, '-');
|
| - }
|
| - elsif (/^call/ || /^function/)
|
| - {
|
| - # Function call return data
|
| - }
|
| - else
|
| - {
|
| - $last_line++;
|
| - # Check for exclusion markers
|
| - if (!$no_markers) {
|
| - if (/$EXCL_STOP/) {
|
| - $exclude_flag = 0;
|
| - } elsif (/$EXCL_START/) {
|
| - $exclude_flag = 1;
|
| - }
|
| - if (/$EXCL_LINE/ || $exclude_flag) {
|
| - $exclude_line = 1;
|
| - } else {
|
| - $exclude_line = 0;
|
| - }
|
| - }
|
| - # Source code execution data
|
| - if (/^\t\t(.*)$/)
|
| - {
|
| - # Uninstrumented line
|
| - push(@result, 0);
|
| - push(@result, 0);
|
| - push(@result, $1);
|
| - next;
|
| - }
|
| - $number = (split(" ",substr($_, 0, 16)))[0];
|
| -
|
| - # Check for zero count which is indicated
|
| - # by ######
|
| - if ($number eq "######") { $number = 0; }
|
| -
|
| - if ($exclude_line) {
|
| - # Register uninstrumented line instead
|
| - push(@result, 0);
|
| - push(@result, 0);
|
| - } else {
|
| - push(@result, 1);
|
| - push(@result, $number);
|
| - }
|
| - push(@result, substr($_, 16));
|
| - }
|
| - }
|
| - }
|
| - else
|
| - {
|
| - # Expect gcov format as used in gcc >= 3.3
|
| - while (<INPUT>)
|
| - {
|
| - chomp($_);
|
| -
|
| - # Also remove CR from line-end
|
| - s/\015$//;
|
| -
|
| - if (/^\s*(\d+|\$+):\s*(\d+)-block\s+(\d+)\s*$/) {
|
| - # Block information - used to group related
|
| - # branches
|
| - $last_line = $2;
|
| - $last_block = $3;
|
| - } elsif (/^branch\s+(\d+)\s+taken\s+(\d+)/) {
|
| - next if ($exclude_line);
|
| - $branches = br_gvec_push($branches, $last_line,
|
| - $last_block, $1, $2);
|
| - } elsif (/^branch\s+(\d+)\s+never\s+executed/) {
|
| - next if ($exclude_line);
|
| - $branches = br_gvec_push($branches, $last_line,
|
| - $last_block, $1, '-');
|
| - }
|
| - elsif (/^function\s+(\S+)\s+called\s+(\d+)/)
|
| - {
|
| - if ($exclude_line) {
|
| - next;
|
| - }
|
| - push(@functions, $2, $1);
|
| - }
|
| - elsif (/^call/)
|
| - {
|
| - # Function call return data
|
| - }
|
| - elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/)
|
| - {
|
| - my ($count, $line, $code) = ($1, $2, $3);
|
| -
|
| - $last_line = $line;
|
| - $last_block = $UNNAMED_BLOCK;
|
| - # Check for exclusion markers
|
| - if (!$no_markers) {
|
| - if (/$EXCL_STOP/) {
|
| - $exclude_flag = 0;
|
| - } elsif (/$EXCL_START/) {
|
| - $exclude_flag = 1;
|
| - }
|
| - if (/$EXCL_LINE/ || $exclude_flag) {
|
| - $exclude_line = 1;
|
| - } else {
|
| - $exclude_line = 0;
|
| - }
|
| - }
|
| - # <exec count>:<line number>:<source code>
|
| - if ($line eq "0")
|
| - {
|
| - # Extra data
|
| - }
|
| - elsif ($count eq "-")
|
| - {
|
| - # Uninstrumented line
|
| - push(@result, 0);
|
| - push(@result, 0);
|
| - push(@result, $code);
|
| - }
|
| - else
|
| - {
|
| - if ($exclude_line) {
|
| - push(@result, 0);
|
| - push(@result, 0);
|
| - } else {
|
| - # Check for zero count
|
| - if ($count eq "#####") {
|
| - $count = 0;
|
| - }
|
| - push(@result, 1);
|
| - push(@result, $count);
|
| - }
|
| - push(@result, $code);
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - close(INPUT);
|
| - if ($exclude_flag) {
|
| - warn("WARNING: unterminated exclusion section in $filename\n");
|
| - }
|
| - return(\@result, $branches, \@functions);
|
| -}
|
| -
|
| -
|
| -#
|
| -# 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->{$_};
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -#
|
| -# get_exclusion_data(filename)
|
| -#
|
| -# Scan specified source code file for exclusion markers and return
|
| -# linenumber -> 1
|
| -# for all lines which should be excluded.
|
| -#
|
| -
|
| -sub get_exclusion_data($)
|
| -{
|
| - my ($filename) = @_;
|
| - my %list;
|
| - my $flag = 0;
|
| - local *HANDLE;
|
| -
|
| - if (!open(HANDLE, "<$filename")) {
|
| - warn("WARNING: could not open $filename\n");
|
| - return undef;
|
| - }
|
| - while (<HANDLE>) {
|
| - if (/$EXCL_STOP/) {
|
| - $flag = 0;
|
| - } elsif (/$EXCL_START/) {
|
| - $flag = 1;
|
| - }
|
| - if (/$EXCL_LINE/ || $flag) {
|
| - $list{$.} = 1;
|
| - }
|
| - }
|
| - close(HANDLE);
|
| -
|
| - if ($flag) {
|
| - warn("WARNING: unterminated exclusion section in $filename\n");
|
| - }
|
| -
|
| - return \%list;
|
| -}
|
| -
|
| -
|
| -#
|
| -# apply_exclusion_data(instr, graph)
|
| -#
|
| -# Remove lines from instr and graph data structures which are marked
|
| -# for exclusion in the source code file.
|
| -#
|
| -# Return adjusted (instr, graph).
|
| -#
|
| -# graph : file name -> function data
|
| -# function data : function name -> line data
|
| -# line data : [ line1, line2, ... ]
|
| -#
|
| -# instr : filename -> line data
|
| -# line data : [ line1, line2, ... ]
|
| -#
|
| -
|
| -sub apply_exclusion_data($$)
|
| -{
|
| - my ($instr, $graph) = @_;
|
| - my $filename;
|
| - my %excl_data;
|
| - my $excl_read_failed = 0;
|
| -
|
| - # Collect exclusion marker data
|
| - foreach $filename (sort_uniq_lex(keys(%{$graph}), keys(%{$instr}))) {
|
| - my $excl = get_exclusion_data($filename);
|
| -
|
| - # Skip and note if file could not be read
|
| - if (!defined($excl)) {
|
| - $excl_read_failed = 1;
|
| - next;
|
| - }
|
| -
|
| - # Add to collection if there are markers
|
| - $excl_data{$filename} = $excl if (keys(%{$excl}) > 0);
|
| - }
|
| -
|
| - # Warn if not all source files could be read
|
| - if ($excl_read_failed) {
|
| - warn("WARNING: some exclusion markers may be ignored\n");
|
| - }
|
| -
|
| - # Skip if no markers were found
|
| - return ($instr, $graph) if (keys(%excl_data) == 0);
|
| -
|
| - # Apply exclusion marker data to graph
|
| - foreach $filename (keys(%excl_data)) {
|
| - my $function_data = $graph->{$filename};
|
| - my $excl = $excl_data{$filename};
|
| - my $function;
|
| -
|
| - next if (!defined($function_data));
|
| -
|
| - foreach $function (keys(%{$function_data})) {
|
| - my $line_data = $function_data->{$function};
|
| - my $line;
|
| - my @new_data;
|
| -
|
| - # To be consistent with exclusion parser in non-initial
|
| - # case we need to remove a function if the first line
|
| - # was excluded
|
| - if ($excl->{$line_data->[0]}) {
|
| - delete($function_data->{$function});
|
| - next;
|
| - }
|
| - # Copy only lines which are not excluded
|
| - foreach $line (@{$line_data}) {
|
| - push(@new_data, $line) if (!$excl->{$line});
|
| - }
|
| -
|
| - # Store modified list
|
| - if (scalar(@new_data) > 0) {
|
| - $function_data->{$function} = \@new_data;
|
| - } else {
|
| - # All of this function was excluded
|
| - delete($function_data->{$function});
|
| - }
|
| - }
|
| -
|
| - # Check if all functions of this file were excluded
|
| - if (keys(%{$function_data}) == 0) {
|
| - delete($graph->{$filename});
|
| - }
|
| - }
|
| -
|
| - # Apply exclusion marker data to instr
|
| - foreach $filename (keys(%excl_data)) {
|
| - my $line_data = $instr->{$filename};
|
| - my $excl = $excl_data{$filename};
|
| - my $line;
|
| - my @new_data;
|
| -
|
| - next if (!defined($line_data));
|
| -
|
| - # Copy only lines which are not excluded
|
| - foreach $line (@{$line_data}) {
|
| - push(@new_data, $line) if (!$excl->{$line});
|
| - }
|
| -
|
| - # Store modified list
|
| - if (scalar(@new_data) > 0) {
|
| - $instr->{$filename} = \@new_data;
|
| - } else {
|
| - # All of this file was excluded
|
| - delete($instr->{$filename});
|
| - }
|
| - }
|
| -
|
| - return ($instr, $graph);
|
| -}
|
| -
|
| -
|
| -sub process_graphfile($$)
|
| -{
|
| - my ($file, $dir) = @_;
|
| - my $graph_filename = $file;
|
| - my $graph_dir;
|
| - my $graph_basename;
|
| - my $source_dir;
|
| - my $base_dir;
|
| - my $graph;
|
| - my $instr;
|
| - my $filename;
|
| - local *INFO_HANDLE;
|
| -
|
| - info("Processing %s\n", abs2rel($file, $dir));
|
| -
|
| - # 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)
|
| - {
|
| - ($instr, $graph) = read_bbg($graph_filename, $base_dir);
|
| - }
|
| - else
|
| - {
|
| - ($instr, $graph) = read_bb($graph_filename, $base_dir);
|
| - }
|
| - }
|
| - else
|
| - {
|
| - ($instr, $graph) = read_gcno($graph_filename, $base_dir);
|
| - }
|
| -
|
| - if (!$no_markers) {
|
| - # Apply exclusion marker data to graph file data
|
| - ($instr, $graph) = apply_exclusion_data($instr, $graph);
|
| - }
|
| -
|
| - # 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 (sort(keys(%{$instr})))
|
| - {
|
| - my $funcdata = $graph->{$filename};
|
| - my $line;
|
| - my $linedata;
|
| -
|
| - print(INFO_HANDLE "SF:$filename\n");
|
| -
|
| - if (defined($funcdata)) {
|
| - my @functions = sort {$funcdata->{$a}->[0] <=>
|
| - $funcdata->{$b}->[0]}
|
| - keys(%{$funcdata});
|
| - my $func;
|
| -
|
| - # Gather list of instrumented lines and functions
|
| - foreach $func (@functions) {
|
| - $linedata = $funcdata->{$func};
|
| -
|
| - # Print function name and starting line
|
| - print(INFO_HANDLE "FN:".$linedata->[0].
|
| - ",".filter_fn_name($func)."\n");
|
| - }
|
| - # Print zero function coverage data
|
| - foreach $func (@functions) {
|
| - print(INFO_HANDLE "FNDA:0,".
|
| - filter_fn_name($func)."\n");
|
| - }
|
| - # Print function summary
|
| - print(INFO_HANDLE "FNF:".scalar(@functions)."\n");
|
| - print(INFO_HANDLE "FNH:0\n");
|
| - }
|
| - # Print zero line coverage data
|
| - foreach $line (@{$instr->{$filename}}) {
|
| - print(INFO_HANDLE "DA:$line,0\n");
|
| - }
|
| - # Print line summary
|
| - print(INFO_HANDLE "LF:".scalar(@{$instr->{$filename}})."\n");
|
| - print(INFO_HANDLE "LH:0\n");
|
| -
|
| - print(INFO_HANDLE "end_of_record\n");
|
| - }
|
| - if (!($output_filename && ($output_filename eq "-")))
|
| - {
|
| - close(INFO_HANDLE);
|
| - }
|
| -}
|
| -
|
| -sub filter_fn_name($)
|
| -{
|
| - my ($fn) = @_;
|
| -
|
| - # Remove characters used internally as function name delimiters
|
| - $fn =~ s/[,=]/_/g;
|
| -
|
| - return $fn;
|
| -}
|
| -
|
| -sub warn_handler($)
|
| -{
|
| - my ($msg) = @_;
|
| -
|
| - warn("$tool_name: $msg");
|
| -}
|
| -
|
| -sub die_handler($)
|
| -{
|
| - my ($msg) = @_;
|
| -
|
| - die("$tool_name: $msg");
|
| -}
|
| -
|
| -
|
| -#
|
| -# graph_error(filename, message)
|
| -#
|
| -# Print message about error in graph file. If ignore_graph_error is set, return.
|
| -# Otherwise abort.
|
| -#
|
| -
|
| -sub graph_error($$)
|
| -{
|
| - my ($filename, $msg) = @_;
|
| -
|
| - if ($ignore[$ERROR_GRAPH]) {
|
| - warn("WARNING: $filename: $msg - skipping\n");
|
| - return;
|
| - }
|
| - die("ERROR: $filename: $msg\n");
|
| -}
|
| -
|
| -#
|
| -# graph_expect(description)
|
| -#
|
| -# If debug is set to a non-zero value, print the specified description of what
|
| -# is expected to be read next from the graph file.
|
| -#
|
| -
|
| -sub graph_expect($)
|
| -{
|
| - my ($msg) = @_;
|
| -
|
| - if (!$debug || !defined($msg)) {
|
| - return;
|
| - }
|
| -
|
| - print(STDERR "DEBUG: expecting $msg\n");
|
| -}
|
| -
|
| -#
|
| -# graph_read(handle, bytes[, description])
|
| -#
|
| -# Read and return the specified number of bytes from handle. Return undef
|
| -# if the number of bytes could not be read.
|
| -#
|
| -
|
| -sub graph_read(*$;$)
|
| -{
|
| - my ($handle, $length, $desc) = @_;
|
| - my $data;
|
| - my $result;
|
| -
|
| - graph_expect($desc);
|
| - $result = read($handle, $data, $length);
|
| - if ($debug) {
|
| - my $ascii = "";
|
| - my $hex = "";
|
| - my $i;
|
| -
|
| - print(STDERR "DEBUG: read($length)=$result: ");
|
| - for ($i = 0; $i < length($data); $i++) {
|
| - my $c = substr($data, $i, 1);;
|
| - my $n = ord($c);
|
| -
|
| - $hex .= sprintf("%02x ", $n);
|
| - if ($n >= 32 && $n <= 127) {
|
| - $ascii .= $c;
|
| - } else {
|
| - $ascii .= ".";
|
| - }
|
| - }
|
| - print(STDERR "$hex |$ascii|");
|
| - print(STDERR "\n");
|
| - }
|
| - if ($result != $length) {
|
| - return undef;
|
| - }
|
| - return $data;
|
| -}
|
| -
|
| -#
|
| -# graph_skip(handle, bytes[, description])
|
| -#
|
| -# Read and discard the specified number of bytes from handle. Return non-zero
|
| -# if bytes could be read, zero otherwise.
|
| -#
|
| -
|
| -sub graph_skip(*$;$)
|
| -{
|
| - my ($handle, $length, $desc) = @_;
|
| -
|
| - if (defined(graph_read($handle, $length, $desc))) {
|
| - return 1;
|
| - }
|
| - return 0;
|
| -}
|
| -
|
| -#
|
| -# sort_uniq(list)
|
| -#
|
| -# Return list in numerically ascending order and without duplicate entries.
|
| -#
|
| -
|
| -sub sort_uniq(@)
|
| -{
|
| - my (@list) = @_;
|
| - my %hash;
|
| -
|
| - foreach (@list) {
|
| - $hash{$_} = 1;
|
| - }
|
| - return sort { $a <=> $b } keys(%hash);
|
| -}
|
| -
|
| -#
|
| -# sort_uniq_lex(list)
|
| -#
|
| -# Return list in lexically ascending order and without duplicate entries.
|
| -#
|
| -
|
| -sub sort_uniq_lex(@)
|
| -{
|
| - my (@list) = @_;
|
| - my %hash;
|
| -
|
| - foreach (@list) {
|
| - $hash{$_} = 1;
|
| - }
|
| - return sort keys(%hash);
|
| -}
|
| -
|
| -#
|
| -# graph_cleanup(graph)
|
| -#
|
| -# Remove entries for functions with no lines. Remove duplicate line numbers.
|
| -# Sort list of line numbers numerically ascending.
|
| -#
|
| -
|
| -sub graph_cleanup($)
|
| -{
|
| - my ($graph) = @_;
|
| - my $filename;
|
| -
|
| - foreach $filename (keys(%{$graph})) {
|
| - my $per_file = $graph->{$filename};
|
| - my $function;
|
| -
|
| - foreach $function (keys(%{$per_file})) {
|
| - my $lines = $per_file->{$function};
|
| -
|
| - if (scalar(@$lines) == 0) {
|
| - # Remove empty function
|
| - delete($per_file->{$function});
|
| - next;
|
| - }
|
| - # Normalize list
|
| - $per_file->{$function} = [ sort_uniq(@$lines) ];
|
| - }
|
| - if (scalar(keys(%{$per_file})) == 0) {
|
| - # Remove empty file
|
| - delete($graph->{$filename});
|
| - }
|
| - }
|
| -}
|
| -
|
| -#
|
| -# graph_find_base(bb)
|
| -#
|
| -# Try to identify the filename which is the base source file for the
|
| -# specified bb data.
|
| -#
|
| -
|
| -sub graph_find_base($)
|
| -{
|
| - my ($bb) = @_;
|
| - my %file_count;
|
| - my $basefile;
|
| - my $file;
|
| - my $func;
|
| - my $filedata;
|
| - my $count;
|
| - my $num;
|
| -
|
| - # Identify base name for this bb data.
|
| - foreach $func (keys(%{$bb})) {
|
| - $filedata = $bb->{$func};
|
| -
|
| - foreach $file (keys(%{$filedata})) {
|
| - $count = $file_count{$file};
|
| -
|
| - # Count file occurrence
|
| - $file_count{$file} = defined($count) ? $count + 1 : 1;
|
| - }
|
| - }
|
| - $count = 0;
|
| - $num = 0;
|
| - foreach $file (keys(%file_count)) {
|
| - if ($file_count{$file} > $count) {
|
| - # The file that contains code for the most functions
|
| - # is likely the base file
|
| - $count = $file_count{$file};
|
| - $num = 1;
|
| - $basefile = $file;
|
| - } elsif ($file_count{$file} == $count) {
|
| - # If more than one file could be the basefile, we
|
| - # don't have a basefile
|
| - $basefile = undef;
|
| - }
|
| - }
|
| -
|
| - return $basefile;
|
| -}
|
| -
|
| -#
|
| -# graph_from_bb(bb, fileorder, bb_filename)
|
| -#
|
| -# Convert data from bb to the graph format and list of instrumented lines.
|
| -# Returns (instr, graph).
|
| -#
|
| -# bb : function name -> file data
|
| -# : undef -> file order
|
| -# file data : filename -> line data
|
| -# line data : [ line1, line2, ... ]
|
| -#
|
| -# file order : function name -> [ filename1, filename2, ... ]
|
| -#
|
| -# graph : file name -> function data
|
| -# function data : function name -> line data
|
| -# line data : [ line1, line2, ... ]
|
| -#
|
| -# instr : filename -> line data
|
| -# line data : [ line1, line2, ... ]
|
| -#
|
| -
|
| -sub graph_from_bb($$$)
|
| -{
|
| - my ($bb, $fileorder, $bb_filename) = @_;
|
| - my $graph = {};
|
| - my $instr = {};
|
| - my $basefile;
|
| - my $file;
|
| - my $func;
|
| - my $filedata;
|
| - my $linedata;
|
| - my $order;
|
| -
|
| - $basefile = graph_find_base($bb);
|
| - # Create graph structure
|
| - foreach $func (keys(%{$bb})) {
|
| - $filedata = $bb->{$func};
|
| - $order = $fileorder->{$func};
|
| -
|
| - # Account for lines in functions
|
| - if (defined($basefile) && defined($filedata->{$basefile})) {
|
| - # If the basefile contributes to this function,
|
| - # account this function to the basefile.
|
| - $graph->{$basefile}->{$func} = $filedata->{$basefile};
|
| - } else {
|
| - # If the basefile does not contribute to this function,
|
| - # account this function to the first file contributing
|
| - # lines.
|
| - $graph->{$order->[0]}->{$func} =
|
| - $filedata->{$order->[0]};
|
| - }
|
| -
|
| - foreach $file (keys(%{$filedata})) {
|
| - # Account for instrumented lines
|
| - $linedata = $filedata->{$file};
|
| - push(@{$instr->{$file}}, @$linedata);
|
| - }
|
| - }
|
| - # Clean up array of instrumented lines
|
| - foreach $file (keys(%{$instr})) {
|
| - $instr->{$file} = [ sort_uniq(@{$instr->{$file}}) ];
|
| - }
|
| -
|
| - return ($instr, $graph);
|
| -}
|
| -
|
| -#
|
| -# graph_add_order(fileorder, function, filename)
|
| -#
|
| -# Add an entry for filename to the fileorder data set for function.
|
| -#
|
| -
|
| -sub graph_add_order($$$)
|
| -{
|
| - my ($fileorder, $function, $filename) = @_;
|
| - my $item;
|
| - my $list;
|
| -
|
| - $list = $fileorder->{$function};
|
| - foreach $item (@$list) {
|
| - if ($item eq $filename) {
|
| - return;
|
| - }
|
| - }
|
| - push(@$list, $filename);
|
| - $fileorder->{$function} = $list;
|
| -}
|
| -#
|
| -# read_bb_word(handle[, description])
|
| -#
|
| -# Read and return a word in .bb format from handle.
|
| -#
|
| -
|
| -sub read_bb_word(*;$)
|
| -{
|
| - my ($handle, $desc) = @_;
|
| -
|
| - return graph_read($handle, 4, $desc);
|
| -}
|
| -
|
| -#
|
| -# read_bb_value(handle[, description])
|
| -#
|
| -# Read a word in .bb format from handle and return the word and its integer
|
| -# value.
|
| -#
|
| -
|
| -sub read_bb_value(*;$)
|
| -{
|
| - my ($handle, $desc) = @_;
|
| - my $word;
|
| -
|
| - $word = read_bb_word($handle, $desc);
|
| - return undef if (!defined($word));
|
| -
|
| - return ($word, unpack("V", $word));
|
| -}
|
| -
|
| -#
|
| -# read_bb_string(handle, delimiter)
|
| -#
|
| -# Read and return a string in .bb format from handle up to the specified
|
| -# delimiter value.
|
| -#
|
| -
|
| -sub read_bb_string(*$)
|
| -{
|
| - my ($handle, $delimiter) = @_;
|
| - my $word;
|
| - my $value;
|
| - my $string = "";
|
| -
|
| - graph_expect("string");
|
| - do {
|
| - ($word, $value) = read_bb_value($handle, "string or delimiter");
|
| - return undef if (!defined($value));
|
| - if ($value != $delimiter) {
|
| - $string .= $word;
|
| - }
|
| - } while ($value != $delimiter);
|
| - $string =~ s/\0//g;
|
| -
|
| - return $string;
|
| -}
|
| -
|
| -#
|
| -# read_bb(filename, base_dir)
|
| -#
|
| -# Read the contents of the specified .bb file and return (instr, graph), where:
|
| -#
|
| -# instr : filename -> line data
|
| -# line data : [ line1, line2, ... ]
|
| -#
|
| -# graph : filename -> file_data
|
| -# file_data : function name -> line_data
|
| -# line_data : [ line1, line2, ... ]
|
| -#
|
| -# Relative filenames are converted to absolute form using base_dir as
|
| -# base directory. See the gcov info pages of gcc 2.95 for a description of
|
| -# the .bb file format.
|
| -#
|
| -
|
| -sub read_bb($$)
|
| -{
|
| - my ($bb_filename, $base) = @_;
|
| - my $minus_one = 0x80000001;
|
| - my $minus_two = 0x80000002;
|
| - my $value;
|
| - my $filename;
|
| - my $function;
|
| - my $bb = {};
|
| - my $fileorder = {};
|
| - my $instr;
|
| - my $graph;
|
| - local *HANDLE;
|
| -
|
| - open(HANDLE, "<$bb_filename") or goto open_error;
|
| - binmode(HANDLE);
|
| - while (!eof(HANDLE)) {
|
| - $value = read_bb_value(*HANDLE, "data word");
|
| - goto incomplete if (!defined($value));
|
| - if ($value == $minus_one) {
|
| - # Source file name
|
| - graph_expect("filename");
|
| - $filename = read_bb_string(*HANDLE, $minus_one);
|
| - goto incomplete if (!defined($filename));
|
| - if ($filename ne "") {
|
| - $filename = solve_relative_path($base,
|
| - $filename);
|
| - }
|
| - } elsif ($value == $minus_two) {
|
| - # Function name
|
| - graph_expect("function name");
|
| - $function = read_bb_string(*HANDLE, $minus_two);
|
| - goto incomplete if (!defined($function));
|
| - } elsif ($value > 0) {
|
| - # Line number
|
| - if (!defined($filename) || !defined($function)) {
|
| - warn("WARNING: unassigned line number ".
|
| - "$value\n");
|
| - next;
|
| - }
|
| - push(@{$bb->{$function}->{$filename}}, $value);
|
| - graph_add_order($fileorder, $function, $filename);
|
| - }
|
| - }
|
| - close(HANDLE);
|
| - ($instr, $graph) = graph_from_bb($bb, $fileorder, $bb_filename);
|
| - graph_cleanup($graph);
|
| -
|
| - return ($instr, $graph);
|
| -
|
| -open_error:
|
| - graph_error($bb_filename, "could not open file");
|
| - return undef;
|
| -incomplete:
|
| - graph_error($bb_filename, "reached unexpected end of file");
|
| - return undef;
|
| -}
|
| -
|
| -#
|
| -# read_bbg_word(handle[, description])
|
| -#
|
| -# Read and return a word in .bbg format.
|
| -#
|
| -
|
| -sub read_bbg_word(*;$)
|
| -{
|
| - my ($handle, $desc) = @_;
|
| -
|
| - return graph_read($handle, 4, $desc);
|
| -}
|
| -
|
| -#
|
| -# read_bbg_value(handle[, description])
|
| -#
|
| -# Read a word in .bbg format from handle and return its integer value.
|
| -#
|
| -
|
| -sub read_bbg_value(*;$)
|
| -{
|
| - my ($handle, $desc) = @_;
|
| - my $word;
|
| -
|
| - $word = read_bbg_word($handle, $desc);
|
| - return undef if (!defined($word));
|
| -
|
| - return unpack("N", $word);
|
| -}
|
| -
|
| -#
|
| -# read_bbg_string(handle)
|
| -#
|
| -# Read and return a string in .bbg format.
|
| -#
|
| -
|
| -sub read_bbg_string(*)
|
| -{
|
| - my ($handle, $desc) = @_;
|
| - my $length;
|
| - my $string;
|
| -
|
| - graph_expect("string");
|
| - # Read string length
|
| - $length = read_bbg_value($handle, "string length");
|
| - return undef if (!defined($length));
|
| - if ($length == 0) {
|
| - return "";
|
| - }
|
| - # Read string
|
| - $string = graph_read($handle, $length, "string");
|
| - return undef if (!defined($string));
|
| - # Skip padding
|
| - graph_skip($handle, 4 - $length % 4, "string padding") or return undef;
|
| -
|
| - return $string;
|
| -}
|
| -
|
| -#
|
| -# read_bbg_lines_record(handle, bbg_filename, bb, fileorder, filename,
|
| -# function, base)
|
| -#
|
| -# Read a bbg format lines record from handle and add the relevant data to
|
| -# bb and fileorder. Return filename on success, undef on error.
|
| -#
|
| -
|
| -sub read_bbg_lines_record(*$$$$$$)
|
| -{
|
| - my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function,
|
| - $base) = @_;
|
| - my $string;
|
| - my $lineno;
|
| -
|
| - graph_expect("lines record");
|
| - # Skip basic block index
|
| - graph_skip($handle, 4, "basic block index") or return undef;
|
| - while (1) {
|
| - # Read line number
|
| - $lineno = read_bbg_value($handle, "line number");
|
| - return undef if (!defined($lineno));
|
| - if ($lineno == 0) {
|
| - # Got a marker for a new filename
|
| - graph_expect("filename");
|
| - $string = read_bbg_string($handle);
|
| - return undef if (!defined($string));
|
| - # Check for end of record
|
| - if ($string eq "") {
|
| - return $filename;
|
| - }
|
| - $filename = solve_relative_path($base, $string);
|
| - next;
|
| - }
|
| - # Got an actual line number
|
| - if (!defined($filename)) {
|
| - warn("WARNING: unassigned line number in ".
|
| - "$bbg_filename\n");
|
| - next;
|
| - }
|
| - push(@{$bb->{$function}->{$filename}}, $lineno);
|
| - graph_add_order($fileorder, $function, $filename);
|
| - }
|
| -}
|
| -
|
| -#
|
| -# read_bbg(filename, base_dir)
|
| -#
|
| -# Read the contents of the specified .bbg file and return the following mapping:
|
| -# graph: filename -> file_data
|
| -# file_data: function name -> line_data
|
| -# line_data: [ line1, line2, ... ]
|
| -#
|
| -# Relative filenames are converted to absolute form using base_dir as
|
| -# base directory. See the gcov-io.h file in the SLES 9 gcc 3.3.3 source code
|
| -# for a description of the .bbg format.
|
| -#
|
| -
|
| -sub read_bbg($$)
|
| -{
|
| - my ($bbg_filename, $base) = @_;
|
| - my $file_magic = 0x67626267;
|
| - my $tag_function = 0x01000000;
|
| - my $tag_lines = 0x01450000;
|
| - my $word;
|
| - my $tag;
|
| - my $length;
|
| - my $function;
|
| - my $filename;
|
| - my $bb = {};
|
| - my $fileorder = {};
|
| - my $instr;
|
| - my $graph;
|
| - local *HANDLE;
|
| -
|
| - open(HANDLE, "<$bbg_filename") or goto open_error;
|
| - binmode(HANDLE);
|
| - # Read magic
|
| - $word = read_bbg_value(*HANDLE, "file magic");
|
| - goto incomplete if (!defined($word));
|
| - # Check magic
|
| - if ($word != $file_magic) {
|
| - goto magic_error;
|
| - }
|
| - # Skip version
|
| - graph_skip(*HANDLE, 4, "version") or goto incomplete;
|
| - while (!eof(HANDLE)) {
|
| - # Read record tag
|
| - $tag = read_bbg_value(*HANDLE, "record tag");
|
| - goto incomplete if (!defined($tag));
|
| - # Read record length
|
| - $length = read_bbg_value(*HANDLE, "record length");
|
| - goto incomplete if (!defined($tag));
|
| - if ($tag == $tag_function) {
|
| - graph_expect("function record");
|
| - # Read function name
|
| - graph_expect("function name");
|
| - $function = read_bbg_string(*HANDLE);
|
| - goto incomplete if (!defined($function));
|
| - $filename = undef;
|
| - # Skip function checksum
|
| - graph_skip(*HANDLE, 4, "function checksum")
|
| - or goto incomplete;
|
| - } elsif ($tag == $tag_lines) {
|
| - # Read lines record
|
| - $filename = read_bbg_lines_record(HANDLE, $bbg_filename,
|
| - $bb, $fileorder, $filename,
|
| - $function, $base);
|
| - goto incomplete if (!defined($filename));
|
| - } else {
|
| - # Skip record contents
|
| - graph_skip(*HANDLE, $length, "unhandled record")
|
| - or goto incomplete;
|
| - }
|
| - }
|
| - close(HANDLE);
|
| - ($instr, $graph) = graph_from_bb($bb, $fileorder, $bbg_filename);
|
| - graph_cleanup($graph);
|
| -
|
| - return ($instr, $graph);
|
| -
|
| -open_error:
|
| - graph_error($bbg_filename, "could not open file");
|
| - return undef;
|
| -incomplete:
|
| - graph_error($bbg_filename, "reached unexpected end of file");
|
| - return undef;
|
| -magic_error:
|
| - graph_error($bbg_filename, "found unrecognized bbg file magic");
|
| - return undef;
|
| -}
|
| -
|
| -#
|
| -# read_gcno_word(handle[, description])
|
| -#
|
| -# Read and return a word in .gcno format.
|
| -#
|
| -
|
| -sub read_gcno_word(*;$)
|
| -{
|
| - my ($handle, $desc) = @_;
|
| -
|
| - return graph_read($handle, 4, $desc);
|
| -}
|
| -
|
| -#
|
| -# read_gcno_value(handle, big_endian[, description])
|
| -#
|
| -# Read a word in .gcno format from handle and return its integer value
|
| -# according to the specified endianness.
|
| -#
|
| -
|
| -sub read_gcno_value(*$;$)
|
| -{
|
| - my ($handle, $big_endian, $desc) = @_;
|
| - my $word;
|
| -
|
| - $word = read_gcno_word($handle, $desc);
|
| - return undef if (!defined($word));
|
| - if ($big_endian) {
|
| - return unpack("N", $word);
|
| - } else {
|
| - return unpack("V", $word);
|
| - }
|
| -}
|
| -
|
| -#
|
| -# read_gcno_string(handle, big_endian)
|
| -#
|
| -# Read and return a string in .gcno format.
|
| -#
|
| -
|
| -sub read_gcno_string(*$)
|
| -{
|
| - my ($handle, $big_endian) = @_;
|
| - my $length;
|
| - my $string;
|
| -
|
| - graph_expect("string");
|
| - # Read string length
|
| - $length = read_gcno_value($handle, $big_endian, "string length");
|
| - return undef if (!defined($length));
|
| - if ($length == 0) {
|
| - return "";
|
| - }
|
| - $length *= 4;
|
| - # Read string
|
| - $string = graph_read($handle, $length, "string and padding");
|
| - return undef if (!defined($string));
|
| - $string =~ s/\0//g;
|
| -
|
| - return $string;
|
| -}
|
| -
|
| -#
|
| -# read_gcno_lines_record(handle, gcno_filename, bb, fileorder, filename,
|
| -# function, base, big_endian)
|
| -#
|
| -# Read a gcno format lines record from handle and add the relevant data to
|
| -# bb and fileorder. Return filename on success, undef on error.
|
| -#
|
| -
|
| -sub read_gcno_lines_record(*$$$$$$$)
|
| -{
|
| - my ($handle, $gcno_filename, $bb, $fileorder, $filename, $function,
|
| - $base, $big_endian) = @_;
|
| - my $string;
|
| - my $lineno;
|
| -
|
| - graph_expect("lines record");
|
| - # Skip basic block index
|
| - graph_skip($handle, 4, "basic block index") or return undef;
|
| - while (1) {
|
| - # Read line number
|
| - $lineno = read_gcno_value($handle, $big_endian, "line number");
|
| - return undef if (!defined($lineno));
|
| - if ($lineno == 0) {
|
| - # Got a marker for a new filename
|
| - graph_expect("filename");
|
| - $string = read_gcno_string($handle, $big_endian);
|
| - return undef if (!defined($string));
|
| - # Check for end of record
|
| - if ($string eq "") {
|
| - return $filename;
|
| - }
|
| - $filename = solve_relative_path($base, $string);
|
| - next;
|
| - }
|
| - # Got an actual line number
|
| - if (!defined($filename)) {
|
| - warn("WARNING: unassigned line number in ".
|
| - "$gcno_filename\n");
|
| - next;
|
| - }
|
| - # Add to list
|
| - push(@{$bb->{$function}->{$filename}}, $lineno);
|
| - graph_add_order($fileorder, $function, $filename);
|
| - }
|
| -}
|
| -
|
| -#
|
| -# read_gcno_function_record(handle, graph, base, big_endian)
|
| -#
|
| -# Read a gcno format function record from handle and add the relevant data
|
| -# to graph. Return (filename, function) on success, undef on error.
|
| -#
|
| -
|
| -sub read_gcno_function_record(*$$$$)
|
| -{
|
| - my ($handle, $bb, $fileorder, $base, $big_endian) = @_;
|
| - my $filename;
|
| - my $function;
|
| - my $lineno;
|
| - my $lines;
|
| -
|
| - graph_expect("function record");
|
| - # Skip ident and checksum
|
| - graph_skip($handle, 8, "function ident and checksum") or return undef;
|
| - # Read function name
|
| - graph_expect("function name");
|
| - $function = read_gcno_string($handle, $big_endian);
|
| - return undef if (!defined($function));
|
| - # Read filename
|
| - graph_expect("filename");
|
| - $filename = read_gcno_string($handle, $big_endian);
|
| - return undef if (!defined($filename));
|
| - $filename = solve_relative_path($base, $filename);
|
| - # Read first line number
|
| - $lineno = read_gcno_value($handle, $big_endian, "initial line number");
|
| - return undef if (!defined($lineno));
|
| - # Add to list
|
| - push(@{$bb->{$function}->{$filename}}, $lineno);
|
| - graph_add_order($fileorder, $function, $filename);
|
| -
|
| - return ($filename, $function);
|
| -}
|
| -
|
| -#
|
| -# read_gcno(filename, base_dir)
|
| -#
|
| -# Read the contents of the specified .gcno file and return the following
|
| -# mapping:
|
| -# graph: filename -> file_data
|
| -# file_data: function name -> line_data
|
| -# line_data: [ line1, line2, ... ]
|
| -#
|
| -# Relative filenames are converted to absolute form using base_dir as
|
| -# base directory. See the gcov-io.h file in the gcc 3.3 source code
|
| -# for a description of the .gcno format.
|
| -#
|
| -
|
| -sub read_gcno($$)
|
| -{
|
| - my ($gcno_filename, $base) = @_;
|
| - my $file_magic = 0x67636e6f;
|
| - my $tag_function = 0x01000000;
|
| - my $tag_lines = 0x01450000;
|
| - my $big_endian;
|
| - my $word;
|
| - my $tag;
|
| - my $length;
|
| - my $filename;
|
| - my $function;
|
| - my $bb = {};
|
| - my $fileorder = {};
|
| - my $instr;
|
| - my $graph;
|
| - local *HANDLE;
|
| -
|
| - open(HANDLE, "<$gcno_filename") or goto open_error;
|
| - binmode(HANDLE);
|
| - # Read magic
|
| - $word = read_gcno_word(*HANDLE, "file magic");
|
| - goto incomplete if (!defined($word));
|
| - # Determine file endianness
|
| - if (unpack("N", $word) == $file_magic) {
|
| - $big_endian = 1;
|
| - } elsif (unpack("V", $word) == $file_magic) {
|
| - $big_endian = 0;
|
| - } else {
|
| - goto magic_error;
|
| - }
|
| - # Skip version and stamp
|
| - graph_skip(*HANDLE, 8, "version and stamp") or goto incomplete;
|
| - while (!eof(HANDLE)) {
|
| - my $next_pos;
|
| - my $curr_pos;
|
| -
|
| - # Read record tag
|
| - $tag = read_gcno_value(*HANDLE, $big_endian, "record tag");
|
| - goto incomplete if (!defined($tag));
|
| - # Read record length
|
| - $length = read_gcno_value(*HANDLE, $big_endian,
|
| - "record length");
|
| - goto incomplete if (!defined($length));
|
| - # Convert length to bytes
|
| - $length *= 4;
|
| - # Calculate start of next record
|
| - $next_pos = tell(HANDLE);
|
| - goto tell_error if ($next_pos == -1);
|
| - $next_pos += $length;
|
| - # Process record
|
| - if ($tag == $tag_function) {
|
| - ($filename, $function) = read_gcno_function_record(
|
| - *HANDLE, $bb, $fileorder, $base, $big_endian);
|
| - goto incomplete if (!defined($function));
|
| - } elsif ($tag == $tag_lines) {
|
| - # Read lines record
|
| - $filename = read_gcno_lines_record(*HANDLE,
|
| - $gcno_filename, $bb, $fileorder,
|
| - $filename, $function, $base,
|
| - $big_endian);
|
| - goto incomplete if (!defined($filename));
|
| - } else {
|
| - # Skip record contents
|
| - graph_skip(*HANDLE, $length, "unhandled record")
|
| - or goto incomplete;
|
| - }
|
| - # Ensure that we are at the start of the next record
|
| - $curr_pos = tell(HANDLE);
|
| - goto tell_error if ($curr_pos == -1);
|
| - next if ($curr_pos == $next_pos);
|
| - goto record_error if ($curr_pos > $next_pos);
|
| - graph_skip(*HANDLE, $next_pos - $curr_pos,
|
| - "unhandled record content")
|
| - or goto incomplete;
|
| - }
|
| - close(HANDLE);
|
| - ($instr, $graph) = graph_from_bb($bb, $fileorder, $gcno_filename);
|
| - graph_cleanup($graph);
|
| -
|
| - return ($instr, $graph);
|
| -
|
| -open_error:
|
| - graph_error($gcno_filename, "could not open file");
|
| - return undef;
|
| -incomplete:
|
| - graph_error($gcno_filename, "reached unexpected end of file");
|
| - return undef;
|
| -magic_error:
|
| - graph_error($gcno_filename, "found unrecognized gcno file magic");
|
| - return undef;
|
| -tell_error:
|
| - graph_error($gcno_filename, "could not determine file position");
|
| - return undef;
|
| -record_error:
|
| - graph_error($gcno_filename, "found unrecognized record format");
|
| - return undef;
|
| -}
|
| -
|
| -sub debug($)
|
| -{
|
| - my ($msg) = @_;
|
| -
|
| - return if (!$debug);
|
| - print(STDERR "DEBUG: $msg");
|
| -}
|
| -
|
| -#
|
| -# get_gcov_capabilities
|
| -#
|
| -# Determine the list of available gcov options.
|
| -#
|
| -
|
| -sub get_gcov_capabilities()
|
| -{
|
| - my $help = `$gcov_tool --help`;
|
| - my %capabilities;
|
| -
|
| - foreach (split(/\n/, $help)) {
|
| - next if (!/--(\S+)/);
|
| - next if ($1 eq 'help');
|
| - next if ($1 eq 'version');
|
| - next if ($1 eq 'object-directory');
|
| -
|
| - $capabilities{$1} = 1;
|
| - debug("gcov has capability '$1'\n");
|
| - }
|
| -
|
| - return \%capabilities;
|
| -}
|
|
|