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