OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/perl -w |
| 2 # |
| 3 # Copyright (c) International Business Machines Corp., 2002,2007 |
| 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@de.ibm.com> |
| 30 # IBM Lab Boeblingen |
| 31 # based on code by Manoj Iyer <manjo@mail.utexas.edu> and |
| 32 # Megan Bock <mbock@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 Getopt::Long; |
| 55 use Digest::MD5 qw(md5_base64); |
| 56 |
| 57 |
| 58 # Constants |
| 59 our $lcov_version = "LCOV version 1.7"; |
| 60 our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; |
| 61 our $gcov_tool = "gcov"; |
| 62 our $tool_name = basename($0); |
| 63 |
| 64 our $GCOV_VERSION_3_4_0 = 0x30400; |
| 65 our $GCOV_VERSION_3_3_0 = 0x30300; |
| 66 our $GCNO_FUNCTION_TAG = 0x01000000; |
| 67 our $GCNO_LINES_TAG = 0x01450000; |
| 68 our $GCNO_FILE_MAGIC = 0x67636e6f; |
| 69 our $BBG_FILE_MAGIC = 0x67626267; |
| 70 |
| 71 our $COMPAT_HAMMER = "hammer"; |
| 72 |
| 73 our $ERROR_GCOV = 0; |
| 74 our $ERROR_SOURCE = 1; |
| 75 |
| 76 # Prototypes |
| 77 sub print_usage(*); |
| 78 sub gen_info($); |
| 79 sub process_dafile($); |
| 80 sub match_filename($@); |
| 81 sub solve_ambiguous_match($$$); |
| 82 sub split_filename($); |
| 83 sub solve_relative_path($$); |
| 84 sub get_dir($); |
| 85 sub read_gcov_header($); |
| 86 sub read_gcov_file($); |
| 87 sub read_bb_file($$); |
| 88 sub read_string(*$); |
| 89 sub read_gcno_file($$); |
| 90 sub read_gcno_string(*$); |
| 91 sub read_hammer_bbg_file($$); |
| 92 sub read_hammer_bbg_string(*$); |
| 93 sub unpack_int32($$); |
| 94 sub info(@); |
| 95 sub get_gcov_version(); |
| 96 sub system_no_output($@); |
| 97 sub read_config($); |
| 98 sub apply_config($); |
| 99 sub gen_initial_info($); |
| 100 sub process_graphfile($); |
| 101 sub warn_handler($); |
| 102 sub die_handler($); |
| 103 |
| 104 # Global variables |
| 105 our $gcov_version; |
| 106 our $graph_file_extension; |
| 107 our $data_file_extension; |
| 108 our @data_directory; |
| 109 our $test_name = ""; |
| 110 our $quiet; |
| 111 our $help; |
| 112 our $output_filename; |
| 113 our $base_directory; |
| 114 our $version; |
| 115 our $follow; |
| 116 our $checksum; |
| 117 our $no_checksum; |
| 118 our $preserve_paths; |
| 119 our $compat_libtool; |
| 120 our $no_compat_libtool; |
| 121 our $adjust_testname; |
| 122 our $config; # Configuration file contents |
| 123 our $compatibility; # Compatibility version flag - used to indicate |
| 124 # non-standard GCOV data format versions |
| 125 our @ignore_errors; # List of errors to ignore (parameter) |
| 126 our @ignore; # List of errors to ignore (array) |
| 127 our $initial; |
| 128 our $no_recursion = 0; |
| 129 our $maxdepth; |
| 130 |
| 131 our $cwd = `pwd`; |
| 132 chomp($cwd); |
| 133 |
| 134 |
| 135 # |
| 136 # Code entry point |
| 137 # |
| 138 |
| 139 # Register handler routine to be called when interrupted |
| 140 $SIG{"INT"} = \&int_handler; |
| 141 $SIG{__WARN__} = \&warn_handler; |
| 142 $SIG{__DIE__} = \&die_handler; |
| 143 |
| 144 # Read configuration file if available |
| 145 if (-r $ENV{"HOME"}."/.lcovrc") |
| 146 { |
| 147 $config = read_config($ENV{"HOME"}."/.lcovrc"); |
| 148 } |
| 149 elsif (-r "/etc/lcovrc") |
| 150 { |
| 151 $config = read_config("/etc/lcovrc"); |
| 152 } |
| 153 |
| 154 if ($config) |
| 155 { |
| 156 # Copy configuration file values to variables |
| 157 apply_config({ |
| 158 "geninfo_gcov_tool" => \$gcov_tool, |
| 159 "geninfo_adjust_testname" => \$adjust_testname, |
| 160 "geninfo_checksum" => \$checksum, |
| 161 "geninfo_no_checksum" => \$no_checksum, # deprecated |
| 162 "geninfo_compat_libtool" => \$compat_libtool}); |
| 163 |
| 164 # Merge options |
| 165 if (defined($no_checksum)) |
| 166 { |
| 167 $checksum = ($no_checksum ? 0 : 1); |
| 168 $no_checksum = undef; |
| 169 } |
| 170 } |
| 171 |
| 172 # Parse command line options |
| 173 if (!GetOptions("test-name=s" => \$test_name, |
| 174 "output-filename=s" => \$output_filename, |
| 175 "checksum" => \$checksum, |
| 176 "no-checksum" => \$no_checksum, |
| 177 "base-directory=s" => \$base_directory, |
| 178 "version" =>\$version, |
| 179 "quiet" => \$quiet, |
| 180 "help|?" => \$help, |
| 181 "follow" => \$follow, |
| 182 "compat-libtool" => \$compat_libtool, |
| 183 "no-compat-libtool" => \$no_compat_libtool, |
| 184 "gcov-tool=s" => \$gcov_tool, |
| 185 "ignore-errors=s" => \@ignore_errors, |
| 186 "initial|i" => \$initial, |
| 187 "no-recursion" => \$no_recursion, |
| 188 )) |
| 189 { |
| 190 print(STDERR "Use $tool_name --help to get usage information\n"); |
| 191 exit(1); |
| 192 } |
| 193 else |
| 194 { |
| 195 # Merge options |
| 196 if (defined($no_checksum)) |
| 197 { |
| 198 $checksum = ($no_checksum ? 0 : 1); |
| 199 $no_checksum = undef; |
| 200 } |
| 201 |
| 202 if (defined($no_compat_libtool)) |
| 203 { |
| 204 $compat_libtool = ($no_compat_libtool ? 0 : 1); |
| 205 $no_compat_libtool = undef; |
| 206 } |
| 207 } |
| 208 |
| 209 @data_directory = @ARGV; |
| 210 |
| 211 # Check for help option |
| 212 if ($help) |
| 213 { |
| 214 print_usage(*STDOUT); |
| 215 exit(0); |
| 216 } |
| 217 |
| 218 # Check for version option |
| 219 if ($version) |
| 220 { |
| 221 print("$tool_name: $lcov_version\n"); |
| 222 exit(0); |
| 223 } |
| 224 |
| 225 # Make sure test names only contain valid characters |
| 226 if ($test_name =~ s/\W/_/g) |
| 227 { |
| 228 warn("WARNING: invalid characters removed from testname!\n"); |
| 229 } |
| 230 |
| 231 # Adjust test name to include uname output if requested |
| 232 if ($adjust_testname) |
| 233 { |
| 234 $test_name .= "__".`uname -a`; |
| 235 $test_name =~ s/\W/_/g; |
| 236 } |
| 237 |
| 238 # Make sure base_directory contains an absolute path specification |
| 239 if ($base_directory) |
| 240 { |
| 241 $base_directory = solve_relative_path($cwd, $base_directory); |
| 242 } |
| 243 |
| 244 # Check for follow option |
| 245 if ($follow) |
| 246 { |
| 247 $follow = "-follow" |
| 248 } |
| 249 else |
| 250 { |
| 251 $follow = ""; |
| 252 } |
| 253 |
| 254 # Determine checksum mode |
| 255 if (defined($checksum)) |
| 256 { |
| 257 # Normalize to boolean |
| 258 $checksum = ($checksum ? 1 : 0); |
| 259 } |
| 260 else |
| 261 { |
| 262 # Default is off |
| 263 $checksum = 0; |
| 264 } |
| 265 |
| 266 # Determine libtool compatibility mode |
| 267 if (defined($compat_libtool)) |
| 268 { |
| 269 $compat_libtool = ($compat_libtool? 1 : 0); |
| 270 } |
| 271 else |
| 272 { |
| 273 # Default is on |
| 274 $compat_libtool = 1; |
| 275 } |
| 276 |
| 277 # Determine max depth for recursion |
| 278 if ($no_recursion) |
| 279 { |
| 280 $maxdepth = "-maxdepth 1"; |
| 281 } |
| 282 else |
| 283 { |
| 284 $maxdepth = ""; |
| 285 } |
| 286 |
| 287 # Check for directory name |
| 288 if (!@data_directory) |
| 289 { |
| 290 die("No directory specified\n". |
| 291 "Use $tool_name --help to get usage information\n"); |
| 292 } |
| 293 else |
| 294 { |
| 295 foreach (@data_directory) |
| 296 { |
| 297 stat($_); |
| 298 if (!-r _) |
| 299 { |
| 300 die("ERROR: cannot read $_!\n"); |
| 301 } |
| 302 } |
| 303 } |
| 304 |
| 305 if (@ignore_errors) |
| 306 { |
| 307 my @expanded; |
| 308 my $error; |
| 309 |
| 310 # Expand comma-separated entries |
| 311 foreach (@ignore_errors) { |
| 312 if (/,/) |
| 313 { |
| 314 push(@expanded, split(",", $_)); |
| 315 } |
| 316 else |
| 317 { |
| 318 push(@expanded, $_); |
| 319 } |
| 320 } |
| 321 |
| 322 foreach (@expanded) |
| 323 { |
| 324 /^gcov$/ && do { $ignore[$ERROR_GCOV] = 1; next; } ; |
| 325 /^source$/ && do { $ignore[$ERROR_SOURCE] = 1; next; }; |
| 326 die("ERROR: unknown argument for --ignore-errors: $_\n"); |
| 327 } |
| 328 } |
| 329 |
| 330 if (system_no_output(3, $gcov_tool, "--help") == -1) |
| 331 { |
| 332 die("ERROR: need tool $gcov_tool!\n"); |
| 333 } |
| 334 |
| 335 $gcov_version = get_gcov_version(); |
| 336 |
| 337 if ($gcov_version < $GCOV_VERSION_3_4_0) |
| 338 { |
| 339 if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) |
| 340 { |
| 341 $data_file_extension = ".da"; |
| 342 $graph_file_extension = ".bbg"; |
| 343 } |
| 344 else |
| 345 { |
| 346 $data_file_extension = ".da"; |
| 347 $graph_file_extension = ".bb"; |
| 348 } |
| 349 } |
| 350 else |
| 351 { |
| 352 $data_file_extension = ".gcda"; |
| 353 $graph_file_extension = ".gcno"; |
| 354 } |
| 355 |
| 356 # Check for availability of --preserve-paths option of gcov |
| 357 if (`$gcov_tool --help` =~ /--preserve-paths/) |
| 358 { |
| 359 $preserve_paths = "--preserve-paths"; |
| 360 } |
| 361 |
| 362 # Check output filename |
| 363 if (defined($output_filename) && ($output_filename ne "-")) |
| 364 { |
| 365 # Initially create output filename, data is appended |
| 366 # for each data file processed |
| 367 local *DUMMY_HANDLE; |
| 368 open(DUMMY_HANDLE, ">$output_filename") |
| 369 or die("ERROR: cannot create $output_filename!\n"); |
| 370 close(DUMMY_HANDLE); |
| 371 |
| 372 # Make $output_filename an absolute path because we're going |
| 373 # to change directories while processing files |
| 374 if (!($output_filename =~ /^\/(.*)$/)) |
| 375 { |
| 376 $output_filename = $cwd."/".$output_filename; |
| 377 } |
| 378 } |
| 379 |
| 380 # Do something |
| 381 if ($initial) |
| 382 { |
| 383 foreach (@data_directory) |
| 384 { |
| 385 gen_initial_info($_); |
| 386 } |
| 387 } |
| 388 else |
| 389 { |
| 390 foreach (@data_directory) |
| 391 { |
| 392 gen_info($_); |
| 393 } |
| 394 } |
| 395 info("Finished .info-file creation\n"); |
| 396 |
| 397 exit(0); |
| 398 |
| 399 |
| 400 |
| 401 # |
| 402 # print_usage(handle) |
| 403 # |
| 404 # Print usage information. |
| 405 # |
| 406 |
| 407 sub print_usage(*) |
| 408 { |
| 409 local *HANDLE = $_[0]; |
| 410 |
| 411 print(HANDLE <<END_OF_USAGE); |
| 412 Usage: $tool_name [OPTIONS] DIRECTORY |
| 413 |
| 414 Traverse DIRECTORY and create a .info file for each data file found. Note |
| 415 that you may specify more than one directory, all of which are then processed |
| 416 sequentially. |
| 417 |
| 418 -h, --help Print this help, then exit |
| 419 -v, --version Print version number, then exit |
| 420 -q, --quiet Do not print progress messages |
| 421 -i, --initial Capture initial zero coverage data |
| 422 -t, --test-name NAME Use test case name NAME for resulting data |
| 423 -o, --output-filename OUTFILE Write data only to OUTFILE |
| 424 -f, --follow Follow links when searching .da/.gcda files |
| 425 -b, --base-directory DIR Use DIR as base directory for relative paths |
| 426 --(no-)checksum Enable (disable) line checksumming |
| 427 --(no-)compat-libtool Enable (disable) libtool compatibility mode |
| 428 --gcov-tool TOOL Specify gcov tool location |
| 429 --ignore-errors ERROR Continue after ERROR (gcov, source) |
| 430 --no-recursion Exlude subdirectories from processing |
| 431 --function-coverage Capture function call counts |
| 432 |
| 433 For more information see: $lcov_url |
| 434 END_OF_USAGE |
| 435 ; |
| 436 } |
| 437 |
| 438 |
| 439 # |
| 440 # gen_info(directory) |
| 441 # |
| 442 # Traverse DIRECTORY and create a .info file for each data file found. |
| 443 # The .info file contains TEST_NAME in the following format: |
| 444 # |
| 445 # TN:<test name> |
| 446 # |
| 447 # For each source file name referenced in the data file, there is a section |
| 448 # containing source code and coverage data: |
| 449 # |
| 450 # SF:<absolute path to the source file> |
| 451 # FN:<line number of function start>,<function name> for each function |
| 452 # DA:<line number>,<execution count> for each instrumented line |
| 453 # LH:<number of lines with an execution count> greater than 0 |
| 454 # LF:<number of instrumented lines> |
| 455 # |
| 456 # Sections are separated by: |
| 457 # |
| 458 # end_of_record |
| 459 # |
| 460 # In addition to the main source code file there are sections for each |
| 461 # #included file containing executable code. Note that the absolute path |
| 462 # of a source file is generated by interpreting the contents of the respective |
| 463 # graph file. Relative filenames are prefixed with the directory in which the |
| 464 # graph file is found. Note also that symbolic links to the graph file will be |
| 465 # resolved so that the actual file path is used instead of the path to a link. |
| 466 # This approach is necessary for the mechanism to work with the /proc/gcov |
| 467 # files. |
| 468 # |
| 469 # Die on error. |
| 470 # |
| 471 |
| 472 sub gen_info($) |
| 473 { |
| 474 my $directory = $_[0]; |
| 475 my @file_list; |
| 476 |
| 477 if (-d $directory) |
| 478 { |
| 479 info("Scanning $directory for $data_file_extension ". |
| 480 "files ...\n"); |
| 481 |
| 482 @file_list = `find "$directory" $maxdepth $follow -name \\*$data
_file_extension -type f 2>/dev/null`; |
| 483 chomp(@file_list); |
| 484 @file_list or die("ERROR: no $data_file_extension files found ". |
| 485 "in $directory!\n"); |
| 486 info("Found %d data files in %s\n", $#file_list+1, $directory); |
| 487 } |
| 488 else |
| 489 { |
| 490 @file_list = ($directory); |
| 491 } |
| 492 |
| 493 # Process all files in list |
| 494 foreach (@file_list) { process_dafile($_); } |
| 495 } |
| 496 |
| 497 |
| 498 # |
| 499 # process_dafile(da_filename) |
| 500 # |
| 501 # Create a .info file for a single data file. |
| 502 # |
| 503 # Die on error. |
| 504 # |
| 505 |
| 506 sub process_dafile($) |
| 507 { |
| 508 info("Processing %s\n", $_[0]); |
| 509 |
| 510 my $da_filename; # Name of data file to process |
| 511 my $da_dir; # Directory of data file |
| 512 my $source_dir; # Directory of source file |
| 513 my $da_basename; # data filename without ".da/.gcda" extension |
| 514 my $bb_filename; # Name of respective graph file |
| 515 my %bb_content; # Contents of graph file |
| 516 my $gcov_error; # Error code of gcov tool |
| 517 my $object_dir; # Directory containing all object files |
| 518 my $source_filename; # Name of a source code file |
| 519 my $gcov_file; # Name of a .gcov file |
| 520 my @gcov_content; # Content of a .gcov file |
| 521 my @gcov_branches; # Branch content of a .gcov file |
| 522 my @gcov_functions; # Function calls of a .gcov file |
| 523 my @gcov_list; # List of generated .gcov files |
| 524 my $line_number; # Line number count |
| 525 my $lines_hit; # Number of instrumented lines hit |
| 526 my $lines_found; # Number of instrumented lines found |
| 527 my $funcs_hit; # Number of instrumented functions hit |
| 528 my $funcs_found; # Number of instrumented functions found |
| 529 my $source; # gcov source header information |
| 530 my $object; # gcov object header information |
| 531 my @matches; # List of absolute paths matching filename |
| 532 my @unprocessed; # List of unprocessed source code files |
| 533 my $base_dir; # Base directory for current file |
| 534 my @result; |
| 535 my $index; |
| 536 my $da_renamed; # If data file is to be renamed |
| 537 local *INFO_HANDLE; |
| 538 |
| 539 # Get path to data file in absolute and normalized form (begins with /, |
| 540 # contains no more ../ or ./) |
| 541 $da_filename = solve_relative_path($cwd, $_[0]); |
| 542 |
| 543 # Get directory and basename of data file |
| 544 ($da_dir, $da_basename) = split_filename($da_filename); |
| 545 |
| 546 # avoid files from .libs dirs |
| 547 if ($compat_libtool && $da_dir =~ m/(.*)\/\.libs$/) { |
| 548 $source_dir = $1; |
| 549 } else { |
| 550 $source_dir = $da_dir; |
| 551 } |
| 552 |
| 553 if (-z $da_filename) |
| 554 { |
| 555 $da_renamed = 1; |
| 556 } |
| 557 else |
| 558 { |
| 559 $da_renamed = 0; |
| 560 } |
| 561 |
| 562 # Construct base_dir for current file |
| 563 if ($base_directory) |
| 564 { |
| 565 $base_dir = $base_directory; |
| 566 } |
| 567 else |
| 568 { |
| 569 $base_dir = $source_dir; |
| 570 } |
| 571 |
| 572 # Check for writable $base_dir (gcov will try to write files there) |
| 573 stat($base_dir); |
| 574 if (!-w _) |
| 575 { |
| 576 die("ERROR: cannot write to directory $base_dir!\n"); |
| 577 } |
| 578 |
| 579 # Construct name of graph file |
| 580 $bb_filename = $da_dir."/".$da_basename.$graph_file_extension; |
| 581 |
| 582 # Find out the real location of graph file in case we're just looking at |
| 583 # a link |
| 584 while (readlink($bb_filename)) |
| 585 { |
| 586 my $last_dir = dirname($bb_filename); |
| 587 |
| 588 $bb_filename = readlink($bb_filename); |
| 589 $bb_filename = solve_relative_path($last_dir, $bb_filename); |
| 590 } |
| 591 |
| 592 # Ignore empty graph file (e.g. source file with no statement) |
| 593 if (-z $bb_filename) |
| 594 { |
| 595 warn("WARNING: empty $bb_filename (skipped)\n"); |
| 596 return; |
| 597 } |
| 598 |
| 599 # Read contents of graph file into hash. We need it later to find out |
| 600 # the absolute path to each .gcov file created as well as for |
| 601 # information about functions and their source code positions. |
| 602 if ($gcov_version < $GCOV_VERSION_3_4_0) |
| 603 { |
| 604 if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) |
| 605 { |
| 606 %bb_content = read_hammer_bbg_file($bb_filename, |
| 607 $base_dir); |
| 608 } |
| 609 else |
| 610 { |
| 611 %bb_content = read_bb_file($bb_filename, $base_dir); |
| 612 } |
| 613 } |
| 614 else |
| 615 { |
| 616 %bb_content = read_gcno_file($bb_filename, $base_dir); |
| 617 } |
| 618 |
| 619 # Set $object_dir to real location of object files. This may differ |
| 620 # from $da_dir if the graph file is just a link to the "real" object |
| 621 # file location. |
| 622 $object_dir = dirname($bb_filename); |
| 623 |
| 624 # Is the data file in a different directory? (this happens e.g. with |
| 625 # the gcov-kernel patch) |
| 626 if ($object_dir ne $da_dir) |
| 627 { |
| 628 # Need to create link to data file in $object_dir |
| 629 system("ln", "-s", $da_filename, |
| 630 "$object_dir/$da_basename$data_file_extension") |
| 631 and die ("ERROR: cannot create link $object_dir/". |
| 632 "$da_basename$data_file_extension!\n"); |
| 633 } |
| 634 |
| 635 # Change to directory containing data files and apply GCOV |
| 636 chdir($base_dir); |
| 637 |
| 638 if ($da_renamed) |
| 639 { |
| 640 # Need to rename empty data file to workaround |
| 641 # gcov <= 3.2.x bug (Abort) |
| 642 system_no_output(3, "mv", "$da_filename", "$da_filename.ori") |
| 643 and die ("ERROR: cannot rename $da_filename\n"); |
| 644 } |
| 645 |
| 646 # Execute gcov command and suppress standard output |
| 647 if ($preserve_paths) |
| 648 { |
| 649 $gcov_error = system_no_output(1, $gcov_tool, $da_filename, |
| 650 "-o", $object_dir, |
| 651 "--preserve-paths", |
| 652 "-b"); |
| 653 } |
| 654 else |
| 655 { |
| 656 $gcov_error = system_no_output(1, $gcov_tool, $da_filename, |
| 657 "-o", $object_dir, |
| 658 "-b"); |
| 659 } |
| 660 |
| 661 if ($da_renamed) |
| 662 { |
| 663 system_no_output(3, "mv", "$da_filename.ori", "$da_filename") |
| 664 and die ("ERROR: cannot rename $da_filename.ori"); |
| 665 } |
| 666 |
| 667 # Clean up link |
| 668 if ($object_dir ne $da_dir) |
| 669 { |
| 670 unlink($object_dir."/".$da_basename.$data_file_extension); |
| 671 } |
| 672 |
| 673 if ($gcov_error) |
| 674 { |
| 675 if ($ignore[$ERROR_GCOV]) |
| 676 { |
| 677 warn("WARNING: GCOV failed for $da_filename!\n"); |
| 678 return; |
| 679 } |
| 680 die("ERROR: GCOV failed for $da_filename!\n"); |
| 681 } |
| 682 |
| 683 # Collect data from resulting .gcov files and create .info file |
| 684 @gcov_list = glob("*.gcov"); |
| 685 |
| 686 # Check for files |
| 687 if (!@gcov_list) |
| 688 { |
| 689 warn("WARNING: gcov did not create any files for ". |
| 690 "$da_filename!\n"); |
| 691 } |
| 692 |
| 693 # Check whether we're writing to a single file |
| 694 if ($output_filename) |
| 695 { |
| 696 if ($output_filename eq "-") |
| 697 { |
| 698 *INFO_HANDLE = *STDOUT; |
| 699 } |
| 700 else |
| 701 { |
| 702 # Append to output file |
| 703 open(INFO_HANDLE, ">>$output_filename") |
| 704 or die("ERROR: cannot write to ". |
| 705 "$output_filename!\n"); |
| 706 } |
| 707 } |
| 708 else |
| 709 { |
| 710 # Open .info file for output |
| 711 open(INFO_HANDLE, ">$da_filename.info") |
| 712 or die("ERROR: cannot create $da_filename.info!\n"); |
| 713 } |
| 714 |
| 715 # Write test name |
| 716 printf(INFO_HANDLE "TN:%s\n", $test_name); |
| 717 |
| 718 # Traverse the list of generated .gcov files and combine them into a |
| 719 # single .info file |
| 720 @unprocessed = keys(%bb_content); |
| 721 foreach $gcov_file (@gcov_list) |
| 722 { |
| 723 ($source, $object) = read_gcov_header($gcov_file); |
| 724 |
| 725 if (defined($source)) |
| 726 { |
| 727 $source = solve_relative_path($base_dir, $source); |
| 728 } |
| 729 |
| 730 # gcov will happily create output even if there's no source code |
| 731 # available - this interferes with checksum creation so we need |
| 732 # to pull the emergency brake here. |
| 733 if (defined($source) && ! -r $source && $checksum) |
| 734 { |
| 735 if ($ignore[$ERROR_SOURCE]) |
| 736 { |
| 737 warn("WARNING: could not read source file ". |
| 738 "$source\n"); |
| 739 next; |
| 740 } |
| 741 die("ERROR: could not read source file $source\n"); |
| 742 } |
| 743 |
| 744 @matches = match_filename(defined($source) ? $source : |
| 745 $gcov_file, keys(%bb_content)); |
| 746 |
| 747 # Skip files that are not mentioned in the graph file |
| 748 if (!@matches) |
| 749 { |
| 750 warn("WARNING: cannot find an entry for ".$gcov_file. |
| 751 " in $graph_file_extension file, skipping ". |
| 752 "file!\n"); |
| 753 unlink($gcov_file); |
| 754 next; |
| 755 } |
| 756 |
| 757 # Read in contents of gcov file |
| 758 @result = read_gcov_file($gcov_file); |
| 759 @gcov_content = @{$result[0]}; |
| 760 @gcov_branches = @{$result[1]}; |
| 761 @gcov_functions = @{$result[2]}; |
| 762 |
| 763 # Skip empty files |
| 764 if (!@gcov_content) |
| 765 { |
| 766 warn("WARNING: skipping empty file ".$gcov_file."\n"); |
| 767 unlink($gcov_file); |
| 768 next; |
| 769 } |
| 770 |
| 771 if (scalar(@matches) == 1) |
| 772 { |
| 773 # Just one match |
| 774 $source_filename = $matches[0]; |
| 775 } |
| 776 else |
| 777 { |
| 778 # Try to solve the ambiguity |
| 779 $source_filename = solve_ambiguous_match($gcov_file, |
| 780 \@matches, \@gcov_content); |
| 781 } |
| 782 |
| 783 # Remove processed file from list |
| 784 for ($index = scalar(@unprocessed) - 1; $index >= 0; $index--) |
| 785 { |
| 786 if ($unprocessed[$index] eq $source_filename) |
| 787 { |
| 788 splice(@unprocessed, $index, 1); |
| 789 last; |
| 790 } |
| 791 } |
| 792 |
| 793 # Write absolute path of source file |
| 794 printf(INFO_HANDLE "SF:%s\n", $source_filename); |
| 795 |
| 796 # Write function-related information |
| 797 if (defined($bb_content{$source_filename})) |
| 798 { |
| 799 foreach (split(",",$bb_content{$source_filename})) |
| 800 { |
| 801 my ($fn, $line) = split("=", $_); |
| 802 |
| 803 if ($fn eq "") { |
| 804 next; |
| 805 } |
| 806 |
| 807 # Normalize function name |
| 808 $fn =~ s/\W/_/g; |
| 809 |
| 810 print(INFO_HANDLE "FN:$line,$fn\n"); |
| 811 } |
| 812 } |
| 813 |
| 814 #-- |
| 815 #-- FNDA: <call-count>, <function-name> |
| 816 #-- FNF: overall count of functions |
| 817 #-- FNH: overall count of functions with non-zero call count |
| 818 #-- |
| 819 $funcs_found = 0; |
| 820 $funcs_hit = 0; |
| 821 while (@gcov_functions) |
| 822 { |
| 823 printf(INFO_HANDLE "FNDA:%s,%s\n", |
| 824 $gcov_functions[0], |
| 825 $gcov_functions[1]); |
| 826 $funcs_found++; |
| 827 $funcs_hit++ if $gcov_functions[0]; |
| 828 splice(@gcov_functions,0,2); |
| 829 } |
| 830 if ($funcs_found > 0) { |
| 831 printf(INFO_HANDLE "FNF:%s\n", $funcs_found); |
| 832 printf(INFO_HANDLE "FNH:%s\n", $funcs_hit); |
| 833 } |
| 834 |
| 835 # Reset line counters |
| 836 $line_number = 0; |
| 837 $lines_found = 0; |
| 838 $lines_hit = 0; |
| 839 |
| 840 # Write coverage information for each instrumented line |
| 841 # Note: @gcov_content contains a list of (flag, count, source) |
| 842 # tuple for each source code line |
| 843 while (@gcov_content) |
| 844 { |
| 845 $line_number++; |
| 846 |
| 847 # Check for instrumented line |
| 848 if ($gcov_content[0]) |
| 849 { |
| 850 $lines_found++; |
| 851 printf(INFO_HANDLE "DA:".$line_number.",". |
| 852 $gcov_content[1].($checksum ? |
| 853 ",". md5_base64($gcov_content[2]) : ""). |
| 854 "\n"); |
| 855 |
| 856 # Increase $lines_hit in case of an execution |
| 857 # count>0 |
| 858 if ($gcov_content[1] > 0) { $lines_hit++; } |
| 859 } |
| 860 |
| 861 # Remove already processed data from array |
| 862 splice(@gcov_content,0,3); |
| 863 } |
| 864 |
| 865 #-- |
| 866 #-- BA: <code-line>, <branch-coverage> |
| 867 #-- |
| 868 #-- print one BA line for every branch of a |
| 869 #-- conditional. <branch-coverage> values |
| 870 #-- are: |
| 871 #-- 0 - not executed |
| 872 #-- 1 - executed but not taken |
| 873 #-- 2 - executed and taken |
| 874 #-- |
| 875 while (@gcov_branches) |
| 876 { |
| 877 if ($gcov_branches[0]) |
| 878 { |
| 879 printf(INFO_HANDLE "BA:%s,%s\n", |
| 880 $gcov_branches[0], |
| 881 $gcov_branches[1]); |
| 882 } |
| 883 splice(@gcov_branches,0,2); |
| 884 } |
| 885 |
| 886 # Write line statistics and section separator |
| 887 printf(INFO_HANDLE "LF:%s\n", $lines_found); |
| 888 printf(INFO_HANDLE "LH:%s\n", $lines_hit); |
| 889 print(INFO_HANDLE "end_of_record\n"); |
| 890 |
| 891 # Remove .gcov file after processing |
| 892 unlink($gcov_file); |
| 893 } |
| 894 |
| 895 # Check for files which show up in the graph file but were never |
| 896 # processed |
| 897 if (@unprocessed && @gcov_list) |
| 898 { |
| 899 foreach (@unprocessed) |
| 900 { |
| 901 warn("WARNING: no data found for $_\n"); |
| 902 } |
| 903 } |
| 904 |
| 905 if (!($output_filename && ($output_filename eq "-"))) |
| 906 { |
| 907 close(INFO_HANDLE); |
| 908 } |
| 909 |
| 910 # Change back to initial directory |
| 911 chdir($cwd); |
| 912 } |
| 913 |
| 914 |
| 915 # |
| 916 # solve_relative_path(path, dir) |
| 917 # |
| 918 # Solve relative path components of DIR which, if not absolute, resides in PATH. |
| 919 # |
| 920 |
| 921 sub solve_relative_path($$) |
| 922 { |
| 923 my $path = $_[0]; |
| 924 my $dir = $_[1]; |
| 925 my $result; |
| 926 |
| 927 $result = $dir; |
| 928 # Prepend path if not absolute |
| 929 if ($dir =~ /^[^\/]/) |
| 930 { |
| 931 $result = "$path/$result"; |
| 932 } |
| 933 |
| 934 # Remove // |
| 935 $result =~ s/\/\//\//g; |
| 936 |
| 937 # Remove . |
| 938 $result =~ s/\/\.\//\//g; |
| 939 |
| 940 # Solve .. |
| 941 while ($result =~ s/\/[^\/]+\/\.\.\//\//) |
| 942 { |
| 943 } |
| 944 |
| 945 # Remove preceding .. |
| 946 $result =~ s/^\/\.\.\//\//g; |
| 947 |
| 948 return $result; |
| 949 } |
| 950 |
| 951 |
| 952 # |
| 953 # match_filename(gcov_filename, list) |
| 954 # |
| 955 # Return a list of those entries of LIST which match the relative filename |
| 956 # GCOV_FILENAME. |
| 957 # |
| 958 |
| 959 sub match_filename($@) |
| 960 { |
| 961 my $filename = shift; |
| 962 my @list = @_; |
| 963 my @result; |
| 964 |
| 965 $filename =~ s/^(.*).gcov$/$1/; |
| 966 |
| 967 if ($filename =~ /^\/(.*)$/) |
| 968 { |
| 969 $filename = "$1"; |
| 970 } |
| 971 |
| 972 foreach (@list) |
| 973 { |
| 974 if (/\/\Q$filename\E(.*)$/ && $1 eq "") |
| 975 { |
| 976 @result = (@result, $_); |
| 977 } |
| 978 } |
| 979 return @result; |
| 980 } |
| 981 |
| 982 |
| 983 # |
| 984 # solve_ambiguous_match(rel_filename, matches_ref, gcov_content_ref) |
| 985 # |
| 986 # Try to solve ambiguous matches of mapping (gcov file) -> (source code) file |
| 987 # by comparing source code provided in the GCOV file with that of the files |
| 988 # in MATCHES. REL_FILENAME identifies the relative filename of the gcov |
| 989 # file. |
| 990 # |
| 991 # Return the one real match or die if there is none. |
| 992 # |
| 993 |
| 994 sub solve_ambiguous_match($$$) |
| 995 { |
| 996 my $rel_name = $_[0]; |
| 997 my $matches = $_[1]; |
| 998 my $content = $_[2]; |
| 999 my $filename; |
| 1000 my $index; |
| 1001 my $no_match; |
| 1002 local *SOURCE; |
| 1003 |
| 1004 # Check the list of matches |
| 1005 foreach $filename (@$matches) |
| 1006 { |
| 1007 |
| 1008 # Compare file contents |
| 1009 open(SOURCE, $filename) |
| 1010 or die("ERROR: cannot read $filename!\n"); |
| 1011 |
| 1012 $no_match = 0; |
| 1013 for ($index = 2; <SOURCE>; $index += 3) |
| 1014 { |
| 1015 chomp; |
| 1016 |
| 1017 if ($_ ne @$content[$index]) |
| 1018 { |
| 1019 $no_match = 1; |
| 1020 last; |
| 1021 } |
| 1022 } |
| 1023 |
| 1024 close(SOURCE); |
| 1025 |
| 1026 if (!$no_match) |
| 1027 { |
| 1028 info("Solved source file ambiguity for $rel_name\n"); |
| 1029 return $filename; |
| 1030 } |
| 1031 } |
| 1032 |
| 1033 die("ERROR: could not match gcov data for $rel_name!\n"); |
| 1034 } |
| 1035 |
| 1036 |
| 1037 # |
| 1038 # split_filename(filename) |
| 1039 # |
| 1040 # Return (path, filename, extension) for a given FILENAME. |
| 1041 # |
| 1042 |
| 1043 sub split_filename($) |
| 1044 { |
| 1045 my @path_components = split('/', $_[0]); |
| 1046 my @file_components = split('\.', pop(@path_components)); |
| 1047 my $extension = pop(@file_components); |
| 1048 |
| 1049 return (join("/",@path_components), join(".",@file_components), |
| 1050 $extension); |
| 1051 } |
| 1052 |
| 1053 |
| 1054 # |
| 1055 # get_dir(filename); |
| 1056 # |
| 1057 # Return the directory component of a given FILENAME. |
| 1058 # |
| 1059 |
| 1060 sub get_dir($) |
| 1061 { |
| 1062 my @components = split("/", $_[0]); |
| 1063 pop(@components); |
| 1064 |
| 1065 return join("/", @components); |
| 1066 } |
| 1067 |
| 1068 |
| 1069 # |
| 1070 # read_gcov_header(gcov_filename) |
| 1071 # |
| 1072 # Parse file GCOV_FILENAME and return a list containing the following |
| 1073 # information: |
| 1074 # |
| 1075 # (source, object) |
| 1076 # |
| 1077 # where: |
| 1078 # |
| 1079 # source: complete relative path of the source code file (gcc >= 3.3 only) |
| 1080 # object: name of associated graph file |
| 1081 # |
| 1082 # Die on error. |
| 1083 # |
| 1084 |
| 1085 sub read_gcov_header($) |
| 1086 { |
| 1087 my $source; |
| 1088 my $object; |
| 1089 local *INPUT; |
| 1090 |
| 1091 if (!open(INPUT, $_[0])) |
| 1092 { |
| 1093 if ($ignore_errors[$ERROR_GCOV]) |
| 1094 { |
| 1095 warn("WARNING: cannot read $_[0]!\n"); |
| 1096 return (undef,undef); |
| 1097 } |
| 1098 die("ERROR: cannot read $_[0]!\n"); |
| 1099 } |
| 1100 |
| 1101 while (<INPUT>) |
| 1102 { |
| 1103 chomp($_); |
| 1104 |
| 1105 if (/^\s+-:\s+0:Source:(.*)$/) |
| 1106 { |
| 1107 # Source: header entry |
| 1108 $source = $1; |
| 1109 } |
| 1110 elsif (/^\s+-:\s+0:Object:(.*)$/) |
| 1111 { |
| 1112 # Object: header entry |
| 1113 $object = $1; |
| 1114 } |
| 1115 else |
| 1116 { |
| 1117 last; |
| 1118 } |
| 1119 } |
| 1120 |
| 1121 close(INPUT); |
| 1122 |
| 1123 return ($source, $object); |
| 1124 } |
| 1125 |
| 1126 |
| 1127 # |
| 1128 # read_gcov_file(gcov_filename) |
| 1129 # |
| 1130 # Parse file GCOV_FILENAME (.gcov file format) and return the list: |
| 1131 # (reference to gcov_content, reference to gcov_branch, reference to gcov_func) |
| 1132 # |
| 1133 # gcov_content is a list of 3 elements |
| 1134 # (flag, count, source) for each source code line: |
| 1135 # |
| 1136 # $result[($line_number-1)*3+0] = instrumentation flag for line $line_number |
| 1137 # $result[($line_number-1)*3+1] = execution count for line $line_number |
| 1138 # $result[($line_number-1)*3+2] = source code text for line $line_number |
| 1139 # |
| 1140 # gcov_branch is a list of 2 elements |
| 1141 # (linenumber, branch result) for each branch |
| 1142 # |
| 1143 # gcov_func is a list of 2 elements |
| 1144 # (number of calls, function name) for each function |
| 1145 # |
| 1146 # Die on error. |
| 1147 # |
| 1148 |
| 1149 sub read_gcov_file($) |
| 1150 { |
| 1151 my $filename = $_[0]; |
| 1152 my @result = (); |
| 1153 my @branches = (); |
| 1154 my @functions = (); |
| 1155 my $number; |
| 1156 local *INPUT; |
| 1157 |
| 1158 open(INPUT, $filename) |
| 1159 or die("ERROR: cannot read $filename!\n"); |
| 1160 |
| 1161 if ($gcov_version < $GCOV_VERSION_3_3_0) |
| 1162 { |
| 1163 # Expect gcov format as used in gcc < 3.3 |
| 1164 while (<INPUT>) |
| 1165 { |
| 1166 chomp($_); |
| 1167 |
| 1168 if (/^\t\t(.*)$/) |
| 1169 { |
| 1170 # Uninstrumented line |
| 1171 push(@result, 0); |
| 1172 push(@result, 0); |
| 1173 push(@result, $1); |
| 1174 } |
| 1175 elsif (/^branch/) |
| 1176 { |
| 1177 # Branch execution data |
| 1178 push(@branches, scalar(@result) / 3); |
| 1179 if (/^branch \d+ never executed$/) |
| 1180 { |
| 1181 push(@branches, 0); |
| 1182 } |
| 1183 elsif (/^branch \d+ taken = 0%/) |
| 1184 { |
| 1185 push(@branches, 1); |
| 1186 } |
| 1187 else |
| 1188 { |
| 1189 push(@branches, 2); |
| 1190 } |
| 1191 } |
| 1192 elsif (/^call/ || /^function/) |
| 1193 { |
| 1194 # Function call return data |
| 1195 } |
| 1196 else |
| 1197 { |
| 1198 # Source code execution data |
| 1199 $number = (split(" ",substr($_, 0, 16)))[0]; |
| 1200 |
| 1201 # Check for zero count which is indicated |
| 1202 # by ###### |
| 1203 if ($number eq "######") { $number = 0; } |
| 1204 |
| 1205 push(@result, 1); |
| 1206 push(@result, $number); |
| 1207 push(@result, substr($_, 16)); |
| 1208 } |
| 1209 } |
| 1210 } |
| 1211 else |
| 1212 { |
| 1213 # Expect gcov format as used in gcc >= 3.3 |
| 1214 while (<INPUT>) |
| 1215 { |
| 1216 chomp($_); |
| 1217 |
| 1218 if (/^branch\s+\d+\s+(\S+)\s+(\S+)/) |
| 1219 { |
| 1220 # Branch execution data |
| 1221 push(@branches, scalar(@result) / 3); |
| 1222 if ($1 eq "never") |
| 1223 { |
| 1224 push(@branches, 0); |
| 1225 } |
| 1226 elsif ($2 eq "0%") |
| 1227 { |
| 1228 push(@branches, 1); |
| 1229 } |
| 1230 else |
| 1231 { |
| 1232 push(@branches, 2); |
| 1233 } |
| 1234 } |
| 1235 elsif (/^function\s+(\S+)\s+called\s+(\d+)/) |
| 1236 { |
| 1237 push(@functions, $2, $1); |
| 1238 } |
| 1239 elsif (/^call/) |
| 1240 { |
| 1241 # Function call return data |
| 1242 } |
| 1243 elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/) |
| 1244 { |
| 1245 # <exec count>:<line number>:<source code> |
| 1246 if ($2 eq "0") |
| 1247 { |
| 1248 # Extra data |
| 1249 } |
| 1250 elsif ($1 eq "-") |
| 1251 { |
| 1252 # Uninstrumented line |
| 1253 push(@result, 0); |
| 1254 push(@result, 0); |
| 1255 push(@result, $3); |
| 1256 } |
| 1257 else |
| 1258 { |
| 1259 # Source code execution data |
| 1260 $number = $1; |
| 1261 |
| 1262 # Check for zero count |
| 1263 if ($number eq "#####") { $number = 0; } |
| 1264 |
| 1265 push(@result, 1); |
| 1266 push(@result, $number); |
| 1267 push(@result, $3); |
| 1268 } |
| 1269 } |
| 1270 } |
| 1271 } |
| 1272 |
| 1273 close(INPUT); |
| 1274 return(\@result, \@branches, \@functions); |
| 1275 } |
| 1276 |
| 1277 |
| 1278 # |
| 1279 # read_bb_file(bb_filename, base_dir) |
| 1280 # |
| 1281 # Read .bb file BB_FILENAME and return a hash containing the following |
| 1282 # mapping: |
| 1283 # |
| 1284 # filename -> comma-separated list of pairs (function name=starting |
| 1285 # line number) to indicate the starting line of a function or |
| 1286 # =name to indicate an instrumented line |
| 1287 # |
| 1288 # for each entry in the .bb file. Filenames are absolute, i.e. relative |
| 1289 # filenames are prefixed with BASE_DIR. |
| 1290 # |
| 1291 # Die on error. |
| 1292 # |
| 1293 |
| 1294 sub read_bb_file($$) |
| 1295 { |
| 1296 my $bb_filename = $_[0]; |
| 1297 my $base_dir = $_[1]; |
| 1298 my %result; |
| 1299 my $filename; |
| 1300 my $function_name; |
| 1301 my $minus_one = sprintf("%d", 0x80000001); |
| 1302 my $minus_two = sprintf("%d", 0x80000002); |
| 1303 my $value; |
| 1304 my $packed_word; |
| 1305 local *INPUT; |
| 1306 |
| 1307 open(INPUT, $bb_filename) |
| 1308 or die("ERROR: cannot read $bb_filename!\n"); |
| 1309 |
| 1310 binmode(INPUT); |
| 1311 |
| 1312 # Read data in words of 4 bytes |
| 1313 while (read(INPUT, $packed_word, 4) == 4) |
| 1314 { |
| 1315 # Decode integer in intel byteorder |
| 1316 $value = unpack_int32($packed_word, 0); |
| 1317 |
| 1318 # Note: the .bb file format is documented in GCC info pages |
| 1319 if ($value == $minus_one) |
| 1320 { |
| 1321 # Filename follows |
| 1322 $filename = read_string(*INPUT, $minus_one) |
| 1323 or die("ERROR: incomplete filename in ". |
| 1324 "$bb_filename!\n"); |
| 1325 |
| 1326 # Make path absolute |
| 1327 $filename = solve_relative_path($base_dir, $filename); |
| 1328 |
| 1329 # Insert into hash if not yet present. |
| 1330 # This is necessary because functions declared as |
| 1331 # "inline" are not listed as actual functions in |
| 1332 # .bb files |
| 1333 if (!$result{$filename}) |
| 1334 { |
| 1335 $result{$filename}=""; |
| 1336 } |
| 1337 } |
| 1338 elsif ($value == $minus_two) |
| 1339 { |
| 1340 # Function name follows |
| 1341 $function_name = read_string(*INPUT, $minus_two) |
| 1342 or die("ERROR: incomplete function ". |
| 1343 "name in $bb_filename!\n"); |
| 1344 $function_name =~ s/\W/_/g; |
| 1345 } |
| 1346 elsif ($value > 0) |
| 1347 { |
| 1348 if (defined($filename)) |
| 1349 { |
| 1350 $result{$filename} .= |
| 1351 ($result{$filename} ? "," : ""). |
| 1352 "=$value"; |
| 1353 } |
| 1354 else |
| 1355 { |
| 1356 warn("WARNING: unassigned line". |
| 1357 " number in .bb file ". |
| 1358 "$bb_filename\n"); |
| 1359 } |
| 1360 if ($function_name) |
| 1361 { |
| 1362 # Got a full entry filename, funcname, lineno |
| 1363 # Add to resulting hash |
| 1364 |
| 1365 $result{$filename}.= |
| 1366 ($result{$filename} ? "," : ""). |
| 1367 join("=",($function_name,$value)); |
| 1368 undef($function_name); |
| 1369 } |
| 1370 } |
| 1371 } |
| 1372 close(INPUT); |
| 1373 |
| 1374 if (!scalar(keys(%result))) |
| 1375 { |
| 1376 die("ERROR: no data found in $bb_filename!\n"); |
| 1377 } |
| 1378 return %result; |
| 1379 } |
| 1380 |
| 1381 |
| 1382 # |
| 1383 # read_string(handle, delimiter); |
| 1384 # |
| 1385 # Read and return a string in 4-byte chunks from HANDLE until DELIMITER |
| 1386 # is found. |
| 1387 # |
| 1388 # Return empty string on error. |
| 1389 # |
| 1390 |
| 1391 sub read_string(*$) |
| 1392 { |
| 1393 my $HANDLE = $_[0]; |
| 1394 my $delimiter = $_[1]; |
| 1395 my $string = ""; |
| 1396 my $packed_word; |
| 1397 my $value; |
| 1398 |
| 1399 while (read($HANDLE,$packed_word,4) == 4) |
| 1400 { |
| 1401 $value = unpack_int32($packed_word, 0); |
| 1402 |
| 1403 if ($value == $delimiter) |
| 1404 { |
| 1405 # Remove trailing nil bytes |
| 1406 $/="\0"; |
| 1407 while (chomp($string)) {}; |
| 1408 $/="\n"; |
| 1409 return($string); |
| 1410 } |
| 1411 |
| 1412 $string = $string.$packed_word; |
| 1413 } |
| 1414 return(""); |
| 1415 } |
| 1416 |
| 1417 |
| 1418 # |
| 1419 # read_gcno_file(bb_filename, base_dir) |
| 1420 # |
| 1421 # Read .gcno file BB_FILENAME and return a hash containing the following |
| 1422 # mapping: |
| 1423 # |
| 1424 # filename -> comma-separated list of pairs (function name=starting |
| 1425 # line number) to indicate the starting line of a function or |
| 1426 # =name to indicate an instrumented line |
| 1427 # |
| 1428 # for each entry in the .gcno file. Filenames are absolute, i.e. relative |
| 1429 # filenames are prefixed with BASE_DIR. |
| 1430 # |
| 1431 # Die on error. |
| 1432 # |
| 1433 |
| 1434 sub read_gcno_file($$) |
| 1435 { |
| 1436 my $gcno_filename = $_[0]; |
| 1437 my $base_dir = $_[1]; |
| 1438 my %result; |
| 1439 my $filename; |
| 1440 my $function_name; |
| 1441 my $lineno; |
| 1442 my $length; |
| 1443 my $value; |
| 1444 my $endianness; |
| 1445 my $blocks; |
| 1446 my $packed_word; |
| 1447 my $string; |
| 1448 local *INPUT; |
| 1449 |
| 1450 open(INPUT, $gcno_filename) |
| 1451 or die("ERROR: cannot read $gcno_filename!\n"); |
| 1452 |
| 1453 binmode(INPUT); |
| 1454 |
| 1455 read(INPUT, $packed_word, 4) == 4 |
| 1456 or die("ERROR: Invalid gcno file format\n"); |
| 1457 |
| 1458 $value = unpack_int32($packed_word, 0); |
| 1459 $endianness = !($value == $GCNO_FILE_MAGIC); |
| 1460 |
| 1461 unpack_int32($packed_word, $endianness) == $GCNO_FILE_MAGIC |
| 1462 or die("ERROR: gcno file magic does not match\n"); |
| 1463 |
| 1464 seek(INPUT, 8, 1); |
| 1465 |
| 1466 # Read data in words of 4 bytes |
| 1467 while (read(INPUT, $packed_word, 4) == 4) |
| 1468 { |
| 1469 # Decode integer in intel byteorder |
| 1470 $value = unpack_int32($packed_word, $endianness); |
| 1471 |
| 1472 if ($value == $GCNO_FUNCTION_TAG) |
| 1473 { |
| 1474 # skip length, ident and checksum |
| 1475 seek(INPUT, 12, 1); |
| 1476 (undef, $function_name) = |
| 1477 read_gcno_string(*INPUT, $endianness); |
| 1478 $function_name =~ s/\W/_/g; |
| 1479 (undef, $filename) = |
| 1480 read_gcno_string(*INPUT, $endianness); |
| 1481 $filename = solve_relative_path($base_dir, $filename); |
| 1482 |
| 1483 read(INPUT, $packed_word, 4); |
| 1484 $lineno = unpack_int32($packed_word, $endianness); |
| 1485 |
| 1486 $result{$filename}.= |
| 1487 ($result{$filename} ? "," : ""). |
| 1488 join("=",($function_name,$lineno)); |
| 1489 } |
| 1490 elsif ($value == $GCNO_LINES_TAG) |
| 1491 { |
| 1492 # Check for names of files containing inlined code |
| 1493 # included in this file |
| 1494 read(INPUT, $packed_word, 4); |
| 1495 $length = unpack_int32($packed_word, $endianness); |
| 1496 if ($length > 0) |
| 1497 { |
| 1498 # Block number |
| 1499 read(INPUT, $packed_word, 4); |
| 1500 $length--; |
| 1501 } |
| 1502 while ($length > 0) |
| 1503 { |
| 1504 read(INPUT, $packed_word, 4); |
| 1505 $lineno = unpack_int32($packed_word, |
| 1506 $endianness); |
| 1507 $length--; |
| 1508 if ($lineno != 0) |
| 1509 { |
| 1510 if (defined($filename)) |
| 1511 { |
| 1512 $result{$filename} .= |
| 1513 ($result{$filename} ? ",
" : ""). |
| 1514 "=$lineno"; |
| 1515 } |
| 1516 else |
| 1517 { |
| 1518 warn("WARNING: unassigned line". |
| 1519 " number in .gcno file ". |
| 1520 "$gcno_filename\n"); |
| 1521 } |
| 1522 next; |
| 1523 } |
| 1524 last if ($length == 0); |
| 1525 ($blocks, $string) = |
| 1526 read_gcno_string(*INPUT, $endianness); |
| 1527 if (defined($string)) |
| 1528 { |
| 1529 $filename = $string; |
| 1530 } |
| 1531 if ($blocks > 1) |
| 1532 { |
| 1533 $filename = solve_relative_path( |
| 1534 $base_dir, $filename); |
| 1535 if (!defined($result{$filename})) |
| 1536 { |
| 1537 $result{$filename} = ""; |
| 1538 } |
| 1539 } |
| 1540 $length -= $blocks; |
| 1541 } |
| 1542 } |
| 1543 else |
| 1544 { |
| 1545 read(INPUT, $packed_word, 4); |
| 1546 $length = unpack_int32($packed_word, $endianness); |
| 1547 seek(INPUT, 4 * $length, 1); |
| 1548 } |
| 1549 } |
| 1550 close(INPUT); |
| 1551 |
| 1552 if (!scalar(keys(%result))) |
| 1553 { |
| 1554 die("ERROR: no data found in $gcno_filename!\n"); |
| 1555 } |
| 1556 return %result; |
| 1557 } |
| 1558 |
| 1559 |
| 1560 # |
| 1561 # read_gcno_string(handle, endianness); |
| 1562 # |
| 1563 # Read a string in 4-byte chunks from HANDLE. |
| 1564 # |
| 1565 # Return (number of 4-byte chunks read, string). |
| 1566 # |
| 1567 |
| 1568 sub read_gcno_string(*$) |
| 1569 { |
| 1570 my $handle = $_[0]; |
| 1571 my $endianness = $_[1]; |
| 1572 my $number_of_blocks = 0; |
| 1573 my $string = ""; |
| 1574 my $packed_word; |
| 1575 |
| 1576 read($handle, $packed_word, 4) == 4 |
| 1577 or die("ERROR: reading string\n"); |
| 1578 |
| 1579 $number_of_blocks = unpack_int32($packed_word, $endianness); |
| 1580 |
| 1581 if ($number_of_blocks == 0) |
| 1582 { |
| 1583 return (1, undef); |
| 1584 } |
| 1585 |
| 1586 if (read($handle, $packed_word, 4 * $number_of_blocks) != |
| 1587 4 * $number_of_blocks) |
| 1588 { |
| 1589 my $msg = "invalid string size ".(4 * $number_of_blocks)." in ". |
| 1590 "gcno file at position ".tell($handle)."\n"; |
| 1591 if ($ignore[$ERROR_SOURCE]) |
| 1592 { |
| 1593 warn("WARNING: $msg"); |
| 1594 return (1, undef); |
| 1595 } |
| 1596 else |
| 1597 { |
| 1598 die("ERROR: $msg"); |
| 1599 } |
| 1600 } |
| 1601 |
| 1602 $string = $string . $packed_word; |
| 1603 |
| 1604 # Remove trailing nil bytes |
| 1605 $/="\0"; |
| 1606 while (chomp($string)) {}; |
| 1607 $/="\n"; |
| 1608 |
| 1609 return(1 + $number_of_blocks, $string); |
| 1610 } |
| 1611 |
| 1612 |
| 1613 # |
| 1614 # read_hammer_bbg_file(bb_filename, base_dir) |
| 1615 # |
| 1616 # Read .bbg file BB_FILENAME and return a hash containing the following |
| 1617 # mapping: |
| 1618 # |
| 1619 # filename -> comma-separated list of pairs (function name=starting |
| 1620 # line number) to indicate the starting line of a function or |
| 1621 # =name to indicate an instrumented line |
| 1622 # |
| 1623 # for each entry in the .bbg file. Filenames are absolute, i.e. relative |
| 1624 # filenames are prefixed with BASE_DIR. |
| 1625 # |
| 1626 # Die on error. |
| 1627 # |
| 1628 |
| 1629 sub read_hammer_bbg_file($$) |
| 1630 { |
| 1631 my $bbg_filename = $_[0]; |
| 1632 my $base_dir = $_[1]; |
| 1633 my %result; |
| 1634 my $filename; |
| 1635 my $function_name; |
| 1636 my $first_line; |
| 1637 my $lineno; |
| 1638 my $length; |
| 1639 my $value; |
| 1640 my $endianness; |
| 1641 my $blocks; |
| 1642 my $packed_word; |
| 1643 local *INPUT; |
| 1644 |
| 1645 open(INPUT, $bbg_filename) |
| 1646 or die("ERROR: cannot read $bbg_filename!\n"); |
| 1647 |
| 1648 binmode(INPUT); |
| 1649 |
| 1650 # Read magic |
| 1651 read(INPUT, $packed_word, 4) == 4 |
| 1652 or die("ERROR: invalid bbg file format\n"); |
| 1653 |
| 1654 $endianness = 1; |
| 1655 |
| 1656 unpack_int32($packed_word, $endianness) == $BBG_FILE_MAGIC |
| 1657 or die("ERROR: bbg file magic does not match\n"); |
| 1658 |
| 1659 # Skip version |
| 1660 seek(INPUT, 4, 1); |
| 1661 |
| 1662 # Read data in words of 4 bytes |
| 1663 while (read(INPUT, $packed_word, 4) == 4) |
| 1664 { |
| 1665 # Get record tag |
| 1666 $value = unpack_int32($packed_word, $endianness); |
| 1667 |
| 1668 # Get record length |
| 1669 read(INPUT, $packed_word, 4); |
| 1670 $length = unpack_int32($packed_word, $endianness); |
| 1671 |
| 1672 if ($value == $GCNO_FUNCTION_TAG) |
| 1673 { |
| 1674 # Get function name |
| 1675 ($value, $function_name) = |
| 1676 read_hammer_bbg_string(*INPUT, $endianness); |
| 1677 $function_name =~ s/\W/_/g; |
| 1678 $filename = undef; |
| 1679 $first_line = undef; |
| 1680 |
| 1681 seek(INPUT, $length - $value * 4, 1); |
| 1682 } |
| 1683 elsif ($value == $GCNO_LINES_TAG) |
| 1684 { |
| 1685 # Get linenumber and filename |
| 1686 # Skip block number |
| 1687 seek(INPUT, 4, 1); |
| 1688 $length -= 4; |
| 1689 |
| 1690 while ($length > 0) |
| 1691 { |
| 1692 read(INPUT, $packed_word, 4); |
| 1693 $lineno = unpack_int32($packed_word, |
| 1694 $endianness); |
| 1695 $length -= 4; |
| 1696 if ($lineno != 0) |
| 1697 { |
| 1698 if (!defined($first_line)) |
| 1699 { |
| 1700 $first_line = $lineno; |
| 1701 } |
| 1702 if (defined($filename)) |
| 1703 { |
| 1704 $result{$filename} .= |
| 1705 ($result{$filename} ? ",
" : ""). |
| 1706 "=$lineno"; |
| 1707 } |
| 1708 else |
| 1709 { |
| 1710 warn("WARNING: unassigned line". |
| 1711 " number in .bbg file ". |
| 1712 "$bbg_filename\n"); |
| 1713 } |
| 1714 next; |
| 1715 } |
| 1716 ($blocks, $value) = |
| 1717 read_hammer_bbg_string( |
| 1718 *INPUT, $endianness); |
| 1719 # Add all filenames to result list |
| 1720 if (defined($value)) |
| 1721 { |
| 1722 $value = solve_relative_path( |
| 1723 $base_dir, $value); |
| 1724 if (!defined($result{$value})) |
| 1725 { |
| 1726 $result{$value} = undef; |
| 1727 } |
| 1728 if (!defined($filename)) |
| 1729 { |
| 1730 $filename = $value; |
| 1731 } |
| 1732 } |
| 1733 $length -= $blocks * 4; |
| 1734 |
| 1735 # Got a complete data set? |
| 1736 if (defined($filename) && |
| 1737 defined($first_line) && |
| 1738 defined($function_name)) |
| 1739 { |
| 1740 # Add it to our result hash |
| 1741 if (defined($result{$filename})) |
| 1742 { |
| 1743 $result{$filename} .= |
| 1744 ",$function_name=$first_line"; |
| 1745 } |
| 1746 else |
| 1747 { |
| 1748 $result{$filename} = |
| 1749 "$function_name=$first_line"; |
| 1750 } |
| 1751 $function_name = undef; |
| 1752 $filename = undef; |
| 1753 $first_line = undef; |
| 1754 } |
| 1755 } |
| 1756 } |
| 1757 else |
| 1758 { |
| 1759 # Skip other records |
| 1760 seek(INPUT, $length, 1); |
| 1761 } |
| 1762 } |
| 1763 close(INPUT); |
| 1764 |
| 1765 if (!scalar(keys(%result))) |
| 1766 { |
| 1767 die("ERROR: no data found in $bbg_filename!\n"); |
| 1768 } |
| 1769 return %result; |
| 1770 } |
| 1771 |
| 1772 |
| 1773 # |
| 1774 # read_hammer_bbg_string(handle, endianness); |
| 1775 # |
| 1776 # Read a string in 4-byte chunks from HANDLE. |
| 1777 # |
| 1778 # Return (number of 4-byte chunks read, string). |
| 1779 # |
| 1780 |
| 1781 sub read_hammer_bbg_string(*$) |
| 1782 { |
| 1783 my $handle = $_[0]; |
| 1784 my $endianness = $_[1]; |
| 1785 my $length = 0; |
| 1786 my $string = ""; |
| 1787 my $packed_word; |
| 1788 my $pad; |
| 1789 |
| 1790 read($handle, $packed_word, 4) == 4 |
| 1791 or die("ERROR: reading string\n"); |
| 1792 |
| 1793 $length = unpack_int32($packed_word, $endianness); |
| 1794 $pad = 4 - $length % 4; |
| 1795 |
| 1796 if ($length == 0) |
| 1797 { |
| 1798 return (1, undef); |
| 1799 } |
| 1800 |
| 1801 read($handle, $string, $length) == |
| 1802 $length or die("ERROR: reading string\n"); |
| 1803 seek($handle, $pad, 1); |
| 1804 |
| 1805 return(1 + ($length + $pad) / 4, $string); |
| 1806 } |
| 1807 |
| 1808 # |
| 1809 # unpack_int32(word, endianness) |
| 1810 # |
| 1811 # Interpret 4-byte binary string WORD as signed 32 bit integer in |
| 1812 # endian encoding defined by ENDIANNESS (0=little, 1=big) and return its |
| 1813 # value. |
| 1814 # |
| 1815 |
| 1816 sub unpack_int32($$) |
| 1817 { |
| 1818 return sprintf("%d", unpack($_[1] ? "N" : "V",$_[0])); |
| 1819 } |
| 1820 |
| 1821 |
| 1822 # |
| 1823 # Get the GCOV tool version. Return an integer number which represents the |
| 1824 # GCOV version. Version numbers can be compared using standard integer |
| 1825 # operations. |
| 1826 # |
| 1827 |
| 1828 sub get_gcov_version() |
| 1829 { |
| 1830 local *HANDLE; |
| 1831 my $version_string; |
| 1832 my $result; |
| 1833 |
| 1834 open(GCOV_PIPE, "$gcov_tool -v |") |
| 1835 or die("ERROR: cannot retrieve gcov version!\n"); |
| 1836 $version_string = <GCOV_PIPE>; |
| 1837 close(GCOV_PIPE); |
| 1838 |
| 1839 $result = 0; |
| 1840 if ($version_string =~ /(\d+)\.(\d+)(\.(\d+))?/) |
| 1841 { |
| 1842 if (defined($4)) |
| 1843 { |
| 1844 info("Found gcov version: $1.$2.$4\n"); |
| 1845 $result = $1 << 16 | $2 << 8 | $4; |
| 1846 } |
| 1847 else |
| 1848 { |
| 1849 info("Found gcov version: $1.$2\n"); |
| 1850 $result = $1 << 16 | $2 << 8; |
| 1851 } |
| 1852 } |
| 1853 if ($version_string =~ /suse/i && $result == 0x30303 || |
| 1854 $version_string =~ /mandrake/i && $result == 0x30302) |
| 1855 { |
| 1856 info("Using compatibility mode for GCC 3.3 (hammer)\n"); |
| 1857 $compatibility = $COMPAT_HAMMER; |
| 1858 } |
| 1859 return $result; |
| 1860 } |
| 1861 |
| 1862 |
| 1863 # |
| 1864 # info(printf_parameter) |
| 1865 # |
| 1866 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag |
| 1867 # is not set. |
| 1868 # |
| 1869 |
| 1870 sub info(@) |
| 1871 { |
| 1872 if (!$quiet) |
| 1873 { |
| 1874 # Print info string |
| 1875 if (defined($output_filename) && ($output_filename eq "-")) |
| 1876 { |
| 1877 # Don't interfere with the .info output to STDOUT |
| 1878 printf(STDERR @_); |
| 1879 } |
| 1880 else |
| 1881 { |
| 1882 printf(@_); |
| 1883 } |
| 1884 } |
| 1885 } |
| 1886 |
| 1887 |
| 1888 # |
| 1889 # int_handler() |
| 1890 # |
| 1891 # Called when the script was interrupted by an INT signal (e.g. CTRl-C) |
| 1892 # |
| 1893 |
| 1894 sub int_handler() |
| 1895 { |
| 1896 if ($cwd) { chdir($cwd); } |
| 1897 info("Aborted.\n"); |
| 1898 exit(1); |
| 1899 } |
| 1900 |
| 1901 |
| 1902 # |
| 1903 # system_no_output(mode, parameters) |
| 1904 # |
| 1905 # Call an external program using PARAMETERS while suppressing depending on |
| 1906 # the value of MODE: |
| 1907 # |
| 1908 # MODE & 1: suppress STDOUT |
| 1909 # MODE & 2: suppress STDERR |
| 1910 # |
| 1911 # Return 0 on success, non-zero otherwise. |
| 1912 # |
| 1913 |
| 1914 sub system_no_output($@) |
| 1915 { |
| 1916 my $mode = shift; |
| 1917 my $result; |
| 1918 local *OLD_STDERR; |
| 1919 local *OLD_STDOUT; |
| 1920 |
| 1921 # Save old stdout and stderr handles |
| 1922 ($mode & 1) && open(OLD_STDOUT, ">>&STDOUT"); |
| 1923 ($mode & 2) && open(OLD_STDERR, ">>&STDERR"); |
| 1924 |
| 1925 # Redirect to /dev/null |
| 1926 ($mode & 1) && open(STDOUT, ">/dev/null"); |
| 1927 ($mode & 2) && open(STDERR, ">/dev/null"); |
| 1928 |
| 1929 system(@_); |
| 1930 $result = $?; |
| 1931 |
| 1932 # Close redirected handles |
| 1933 ($mode & 1) && close(STDOUT); |
| 1934 ($mode & 2) && close(STDERR); |
| 1935 |
| 1936 # Restore old handles |
| 1937 ($mode & 1) && open(STDOUT, ">>&OLD_STDOUT"); |
| 1938 ($mode & 2) && open(STDERR, ">>&OLD_STDERR"); |
| 1939 |
| 1940 return $result; |
| 1941 } |
| 1942 |
| 1943 |
| 1944 # |
| 1945 # read_config(filename) |
| 1946 # |
| 1947 # Read configuration file FILENAME and return a reference to a hash containing |
| 1948 # all valid key=value pairs found. |
| 1949 # |
| 1950 |
| 1951 sub read_config($) |
| 1952 { |
| 1953 my $filename = $_[0]; |
| 1954 my %result; |
| 1955 my $key; |
| 1956 my $value; |
| 1957 local *HANDLE; |
| 1958 |
| 1959 if (!open(HANDLE, "<$filename")) |
| 1960 { |
| 1961 warn("WARNING: cannot read configuration file $filename\n"); |
| 1962 return undef; |
| 1963 } |
| 1964 while (<HANDLE>) |
| 1965 { |
| 1966 chomp; |
| 1967 # Skip comments |
| 1968 s/#.*//; |
| 1969 # Remove leading blanks |
| 1970 s/^\s+//; |
| 1971 # Remove trailing blanks |
| 1972 s/\s+$//; |
| 1973 next unless length; |
| 1974 ($key, $value) = split(/\s*=\s*/, $_, 2); |
| 1975 if (defined($key) && defined($value)) |
| 1976 { |
| 1977 $result{$key} = $value; |
| 1978 } |
| 1979 else |
| 1980 { |
| 1981 warn("WARNING: malformed statement in line $. ". |
| 1982 "of configuration file $filename\n"); |
| 1983 } |
| 1984 } |
| 1985 close(HANDLE); |
| 1986 return \%result; |
| 1987 } |
| 1988 |
| 1989 |
| 1990 # |
| 1991 # apply_config(REF) |
| 1992 # |
| 1993 # REF is a reference to a hash containing the following mapping: |
| 1994 # |
| 1995 # key_string => var_ref |
| 1996 # |
| 1997 # where KEY_STRING is a keyword and VAR_REF is a reference to an associated |
| 1998 # variable. If the global configuration hash CONFIG contains a value for |
| 1999 # keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. |
| 2000 # |
| 2001 |
| 2002 sub apply_config($) |
| 2003 { |
| 2004 my $ref = $_[0]; |
| 2005 |
| 2006 foreach (keys(%{$ref})) |
| 2007 { |
| 2008 if (defined($config->{$_})) |
| 2009 { |
| 2010 ${$ref->{$_}} = $config->{$_}; |
| 2011 } |
| 2012 } |
| 2013 } |
| 2014 |
| 2015 |
| 2016 sub gen_initial_info($) |
| 2017 { |
| 2018 my $directory = $_[0]; |
| 2019 my @file_list; |
| 2020 |
| 2021 if (-d $directory) |
| 2022 { |
| 2023 info("Scanning $directory for $graph_file_extension ". |
| 2024 "files ...\n"); |
| 2025 |
| 2026 @file_list = `find "$directory" $maxdepth $follow -name \\*$grap
h_file_extension -type f 2>/dev/null`; |
| 2027 chomp(@file_list); |
| 2028 @file_list or die("ERROR: no $graph_file_extension files ". |
| 2029 "found in $directory!\n"); |
| 2030 info("Found %d graph files in %s\n", $#file_list+1, $directory); |
| 2031 } |
| 2032 else |
| 2033 { |
| 2034 @file_list = ($directory); |
| 2035 } |
| 2036 |
| 2037 # Process all files in list |
| 2038 foreach (@file_list) { process_graphfile($_); } |
| 2039 } |
| 2040 |
| 2041 sub process_graphfile($) |
| 2042 { |
| 2043 my $graph_filename = $_[0]; |
| 2044 my $graph_dir; |
| 2045 my $graph_basename; |
| 2046 my $source_dir; |
| 2047 my $base_dir; |
| 2048 my %graph_data; |
| 2049 my $filename; |
| 2050 local *INFO_HANDLE; |
| 2051 |
| 2052 info("Processing $_[0]\n"); |
| 2053 |
| 2054 # Get path to data file in absolute and normalized form (begins with /, |
| 2055 # contains no more ../ or ./) |
| 2056 $graph_filename = solve_relative_path($cwd, $graph_filename); |
| 2057 |
| 2058 # Get directory and basename of data file |
| 2059 ($graph_dir, $graph_basename) = split_filename($graph_filename); |
| 2060 |
| 2061 # avoid files from .libs dirs |
| 2062 if ($compat_libtool && $graph_dir =~ m/(.*)\/\.libs$/) { |
| 2063 $source_dir = $1; |
| 2064 } else { |
| 2065 $source_dir = $graph_dir; |
| 2066 } |
| 2067 |
| 2068 # Construct base_dir for current file |
| 2069 if ($base_directory) |
| 2070 { |
| 2071 $base_dir = $base_directory; |
| 2072 } |
| 2073 else |
| 2074 { |
| 2075 $base_dir = $source_dir; |
| 2076 } |
| 2077 |
| 2078 if ($gcov_version < $GCOV_VERSION_3_4_0) |
| 2079 { |
| 2080 if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) |
| 2081 { |
| 2082 %graph_data = read_hammer_bbg_file($graph_filename, |
| 2083 $base_dir); |
| 2084 } |
| 2085 else |
| 2086 { |
| 2087 %graph_data = read_bb_file($graph_filename, $base_dir); |
| 2088 } |
| 2089 } |
| 2090 else |
| 2091 { |
| 2092 %graph_data = read_gcno_file($graph_filename, $base_dir); |
| 2093 } |
| 2094 |
| 2095 # Check whether we're writing to a single file |
| 2096 if ($output_filename) |
| 2097 { |
| 2098 if ($output_filename eq "-") |
| 2099 { |
| 2100 *INFO_HANDLE = *STDOUT; |
| 2101 } |
| 2102 else |
| 2103 { |
| 2104 # Append to output file |
| 2105 open(INFO_HANDLE, ">>$output_filename") |
| 2106 or die("ERROR: cannot write to ". |
| 2107 "$output_filename!\n"); |
| 2108 } |
| 2109 } |
| 2110 else |
| 2111 { |
| 2112 # Open .info file for output |
| 2113 open(INFO_HANDLE, ">$graph_filename.info") |
| 2114 or die("ERROR: cannot create $graph_filename.info!\n"); |
| 2115 } |
| 2116 |
| 2117 # Write test name |
| 2118 printf(INFO_HANDLE "TN:%s\n", $test_name); |
| 2119 foreach $filename (keys(%graph_data)) |
| 2120 { |
| 2121 my %lines; |
| 2122 my $count = 0; |
| 2123 my @functions; |
| 2124 |
| 2125 print(INFO_HANDLE "SF:$filename\n"); |
| 2126 |
| 2127 # Write function related data |
| 2128 foreach (split(",",$graph_data{$filename})) |
| 2129 { |
| 2130 my ($fn, $line) = split("=", $_); |
| 2131 |
| 2132 if ($fn eq "") |
| 2133 { |
| 2134 $lines{$line} = ""; |
| 2135 next; |
| 2136 } |
| 2137 |
| 2138 # Normalize function name |
| 2139 $fn =~ s/\W/_/g; |
| 2140 |
| 2141 print(INFO_HANDLE "FN:$line,$fn\n"); |
| 2142 push(@functions, $fn); |
| 2143 } |
| 2144 foreach (@functions) { |
| 2145 print(INFO_HANDLE "FNDA:$_,0\n"); |
| 2146 } |
| 2147 print(INFO_HANDLE "FNF:".scalar(@functions)."\n"); |
| 2148 print(INFO_HANDLE "FNH:0\n"); |
| 2149 |
| 2150 # Write line related data |
| 2151 foreach (sort {$a <=> $b } keys(%lines)) |
| 2152 { |
| 2153 print(INFO_HANDLE "DA:$_,0\n"); |
| 2154 $count++; |
| 2155 } |
| 2156 print(INFO_HANDLE "LH:0\n"); |
| 2157 print(INFO_HANDLE "LF:$count\n"); |
| 2158 print(INFO_HANDLE "end_of_record\n"); |
| 2159 } |
| 2160 if (!($output_filename && ($output_filename eq "-"))) |
| 2161 { |
| 2162 close(INFO_HANDLE); |
| 2163 } |
| 2164 } |
| 2165 |
| 2166 sub warn_handler($) |
| 2167 { |
| 2168 my ($msg) = @_; |
| 2169 |
| 2170 warn("$tool_name: $msg"); |
| 2171 } |
| 2172 |
| 2173 sub die_handler($) |
| 2174 { |
| 2175 my ($msg) = @_; |
| 2176 |
| 2177 die("$tool_name: $msg"); |
| 2178 } |
OLD | NEW |