Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(795)

Side by Side Diff: third_party/lcov-1.9/bin/geninfo

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

Powered by Google App Engine
This is Rietveld 408576698