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