1 | /* Dump a gcov file, for debugging use. |
2 | Copyright (C) 2002-2024 Free Software Foundation, Inc. |
3 | Contributed by Nathan Sidwell <nathan@codesourcery.com> |
4 | |
5 | Gcov 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 3, or (at your option) |
8 | any later version. |
9 | |
10 | Gcov is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | GNU General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU General Public License |
16 | along with Gcov; see the file COPYING3. If not see |
17 | <http://www.gnu.org/licenses/>. */ |
18 | |
19 | #include "config.h" |
20 | #define INCLUDE_VECTOR |
21 | #include "system.h" |
22 | #include "coretypes.h" |
23 | #include "tm.h" |
24 | #include "version.h" |
25 | #include "intl.h" |
26 | #include "diagnostic.h" |
27 | #include <getopt.h> |
28 | #define IN_GCOV (-1) |
29 | #include "gcov-io.h" |
30 | #include "gcov-io.cc" |
31 | |
32 | using namespace std; |
33 | |
34 | static void dump_gcov_file (const char *); |
35 | static void print_prefix (const char *, unsigned, gcov_position_t); |
36 | static void print_usage (void); |
37 | static void print_version (void); |
38 | static void tag_function (const char *, unsigned, int, unsigned); |
39 | static void tag_blocks (const char *, unsigned, int, unsigned); |
40 | static void tag_arcs (const char *, unsigned, int, unsigned); |
41 | static void tag_conditions (const char *, unsigned, int, unsigned); |
42 | static void tag_lines (const char *, unsigned, int, unsigned); |
43 | static void tag_counters (const char *, unsigned, int, unsigned); |
44 | static void tag_summary (const char *, unsigned, int, unsigned); |
45 | extern int main (int, char **); |
46 | |
47 | typedef struct tag_format |
48 | { |
49 | unsigned tag; |
50 | char const *name; |
51 | void (*proc) (const char *, unsigned, int, unsigned); |
52 | } tag_format_t; |
53 | |
54 | static int flag_dump_contents = 0; |
55 | static int flag_dump_positions = 0; |
56 | static int flag_dump_raw = 0; |
57 | static int flag_dump_stable = 0; |
58 | |
59 | static const struct option options[] = |
60 | { |
61 | { .name: "help" , no_argument, NULL, .val: 'h' }, |
62 | { .name: "version" , no_argument, NULL, .val: 'v' }, |
63 | { .name: "long" , no_argument, NULL, .val: 'l' }, |
64 | { .name: "positions" , no_argument, NULL, .val: 'o' }, |
65 | { .name: "raw" , no_argument, NULL, .val: 'r' }, |
66 | { .name: "stable" , no_argument, NULL, .val: 's' }, |
67 | {} |
68 | }; |
69 | |
70 | #define VALUE_PADDING_PREFIX " " |
71 | #define VALUE_PREFIX "%2d: " |
72 | |
73 | static const tag_format_t tag_table[] = |
74 | { |
75 | {.tag: 0, .name: "NOP" , NULL}, |
76 | {.tag: 0, .name: "UNKNOWN" , NULL}, |
77 | {.tag: 0, .name: "COUNTERS" , .proc: tag_counters}, |
78 | {GCOV_TAG_FUNCTION, .name: "FUNCTION" , .proc: tag_function}, |
79 | {GCOV_TAG_BLOCKS, .name: "BLOCKS" , .proc: tag_blocks}, |
80 | {GCOV_TAG_ARCS, .name: "ARCS" , .proc: tag_arcs}, |
81 | {GCOV_TAG_CONDS, .name: "CONDITIONS" , .proc: tag_conditions}, |
82 | {GCOV_TAG_LINES, .name: "LINES" , .proc: tag_lines}, |
83 | {GCOV_TAG_OBJECT_SUMMARY, .name: "OBJECT_SUMMARY" , .proc: tag_summary}, |
84 | {.tag: 0, NULL, NULL} |
85 | }; |
86 | |
87 | int |
88 | main (int argc ATTRIBUTE_UNUSED, char **argv) |
89 | { |
90 | int opt; |
91 | const char *p; |
92 | |
93 | p = argv[0] + strlen (s: argv[0]); |
94 | while (p != argv[0] && !IS_DIR_SEPARATOR (p[-1])) |
95 | --p; |
96 | progname = p; |
97 | |
98 | xmalloc_set_program_name (progname); |
99 | |
100 | /* Unlock the stdio streams. */ |
101 | unlock_std_streams (); |
102 | |
103 | gcc_init_libintl (); |
104 | |
105 | diagnostic_initialize (context: global_dc, n_opts: 0); |
106 | |
107 | while ((opt = getopt_long (argc, argv, shortopts: "hlprsvw" , longopts: options, NULL)) != -1) |
108 | { |
109 | switch (opt) |
110 | { |
111 | case 'h': |
112 | print_usage (); |
113 | break; |
114 | case 'v': |
115 | print_version (); |
116 | break; |
117 | case 'l': |
118 | flag_dump_contents = 1; |
119 | break; |
120 | case 'p': |
121 | flag_dump_positions = 1; |
122 | break; |
123 | case 'r': |
124 | flag_dump_raw = 1; |
125 | break; |
126 | case 's': |
127 | flag_dump_stable = 1; |
128 | break; |
129 | default: |
130 | fprintf (stderr, format: "unknown flag `%c'\n" , opt); |
131 | } |
132 | } |
133 | |
134 | while (argv[optind]) |
135 | dump_gcov_file (argv[optind++]); |
136 | return 0; |
137 | } |
138 | |
139 | static void |
140 | print_usage (void) |
141 | { |
142 | printf (format: "Usage: gcov-dump [OPTION] ... gcovfiles\n" ); |
143 | printf (format: "Print coverage file contents\n" ); |
144 | printf (format: " -h, --help Print this help\n" ); |
145 | printf (format: " -l, --long Dump record contents too\n" ); |
146 | printf (format: " -p, --positions Dump record positions\n" ); |
147 | printf (format: " -r, --raw Print content records in raw format\n" ); |
148 | printf (format: " -s, --stable Print content in stable " |
149 | "format usable for comparison\n" ); |
150 | printf (format: " -v, --version Print version number\n" ); |
151 | printf (format: "\nFor bug reporting instructions, please see:\n%s.\n" , |
152 | bug_report_url); |
153 | } |
154 | |
155 | static void |
156 | print_version (void) |
157 | { |
158 | printf (format: "gcov-dump %s%s\n" , pkgversion_string, version_string); |
159 | printf (format: "Copyright (C) 2024 Free Software Foundation, Inc.\n" ); |
160 | printf (format: "This is free software; see the source for copying conditions. There is NO\n\ |
161 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n" ); |
162 | } |
163 | |
164 | static void |
165 | print_prefix (const char *filename, unsigned depth, gcov_position_t position) |
166 | { |
167 | static const char prefix[] = " " ; |
168 | |
169 | printf (format: "%s:" , filename); |
170 | if (flag_dump_positions) |
171 | printf (format: "%5lu:" , (unsigned long) position); |
172 | printf (format: "%.*s" , (int) 2 * depth, prefix); |
173 | } |
174 | |
175 | static void |
176 | dump_gcov_file (const char *filename) |
177 | { |
178 | unsigned tags[4]; |
179 | unsigned depth = 0; |
180 | bool is_data_type; |
181 | |
182 | if (!gcov_open (name: filename, mode: 1)) |
183 | { |
184 | fprintf (stderr, format: "%s:cannot open\n" , filename); |
185 | return; |
186 | } |
187 | |
188 | /* magic */ |
189 | { |
190 | unsigned magic = gcov_read_unsigned (); |
191 | unsigned version; |
192 | int endianness = 0; |
193 | char m[4], v[4]; |
194 | |
195 | if ((endianness = gcov_magic (magic, GCOV_DATA_MAGIC))) |
196 | is_data_type = true; |
197 | else if ((endianness = gcov_magic (magic, GCOV_NOTE_MAGIC))) |
198 | is_data_type = false; |
199 | else |
200 | { |
201 | printf (format: "%s:not a gcov file\n" , filename); |
202 | gcov_close (); |
203 | return; |
204 | } |
205 | version = gcov_read_unsigned (); |
206 | GCOV_UNSIGNED2STRING (v, version); |
207 | GCOV_UNSIGNED2STRING (m, magic); |
208 | |
209 | printf (format: "%s:%s:magic `%.4s':version `%.4s'%s\n" , filename, |
210 | is_data_type ? "data" : "note" , |
211 | m, v, endianness < 0 ? " (swapped endianness)" : "" ); |
212 | if (version != GCOV_VERSION) |
213 | { |
214 | char e[4]; |
215 | |
216 | GCOV_UNSIGNED2STRING (e, GCOV_VERSION); |
217 | printf (format: "%s:warning:current version is `%.4s'\n" , filename, e); |
218 | } |
219 | } |
220 | |
221 | /* stamp */ |
222 | unsigned stamp = gcov_read_unsigned (); |
223 | printf (format: "%s:stamp %u\n" , filename, stamp); |
224 | |
225 | /* Checksum */ |
226 | unsigned checksum = gcov_read_unsigned (); |
227 | printf (format: "%s:checksum %u\n" , filename, checksum); |
228 | |
229 | if (!is_data_type) |
230 | { |
231 | printf (format: "%s:cwd: %s\n" , filename, gcov_read_string ()); |
232 | |
233 | /* Support for unexecuted basic blocks. */ |
234 | unsigned support_unexecuted_blocks = gcov_read_unsigned (); |
235 | if (!support_unexecuted_blocks) |
236 | printf (format: "%s: has_unexecuted_block is not supported\n" , filename); |
237 | } |
238 | |
239 | while (1) |
240 | { |
241 | gcov_position_t base, position = gcov_position (); |
242 | int read_length; |
243 | unsigned tag, length; |
244 | tag_format_t const *format; |
245 | unsigned tag_depth; |
246 | int error; |
247 | unsigned mask; |
248 | |
249 | tag = gcov_read_unsigned (); |
250 | if (!tag) |
251 | break; |
252 | read_length = (int)gcov_read_unsigned (); |
253 | length = read_length > 0 ? read_length : 0; |
254 | base = gcov_position (); |
255 | mask = GCOV_TAG_MASK (tag) >> 1; |
256 | for (tag_depth = 4; mask; mask >>= 8) |
257 | { |
258 | if ((mask & 0xff) != 0xff) |
259 | { |
260 | printf (format: "%s:tag `%08x' is invalid\n" , filename, tag); |
261 | break; |
262 | } |
263 | tag_depth--; |
264 | } |
265 | for (format = tag_table; format->name; format++) |
266 | if (format->tag == tag) |
267 | goto found; |
268 | format = &tag_table[GCOV_TAG_IS_COUNTER (tag) ? 2 : 1]; |
269 | found:; |
270 | if (tag) |
271 | { |
272 | if (depth && depth < tag_depth) |
273 | { |
274 | if (!GCOV_TAG_IS_SUBTAG (tags[depth - 1], tag)) |
275 | printf (format: "%s:tag `%08x' is incorrectly nested\n" , |
276 | filename, tag); |
277 | } |
278 | depth = tag_depth; |
279 | tags[depth - 1] = tag; |
280 | } |
281 | |
282 | print_prefix (filename, depth: tag_depth, position); |
283 | printf (format: "%08x:%4u:%s" , tag, abs (x: read_length), format->name); |
284 | if (format->proc) |
285 | (*format->proc) (filename, tag, read_length, depth); |
286 | |
287 | printf (format: "\n" ); |
288 | if (flag_dump_contents && format->proc) |
289 | { |
290 | unsigned long actual_length = gcov_position () - base; |
291 | |
292 | if (actual_length > length) |
293 | printf (format: "%s:record size mismatch %lu bytes overread\n" , |
294 | filename, actual_length - length); |
295 | else if (length > actual_length) |
296 | printf (format: "%s:record size mismatch %lu bytes unread\n" , |
297 | filename, length - actual_length); |
298 | } |
299 | gcov_sync (base, length); |
300 | if ((error = gcov_is_error ())) |
301 | { |
302 | printf (format: error < 0 ? "%s:counter overflow at %lu\n" : |
303 | "%s:read error at %lu\n" , filename, |
304 | (long unsigned) gcov_position ()); |
305 | break; |
306 | } |
307 | } |
308 | gcov_close (); |
309 | } |
310 | |
311 | static void |
312 | tag_function (const char *filename ATTRIBUTE_UNUSED, |
313 | unsigned tag ATTRIBUTE_UNUSED, int length, |
314 | unsigned depth ATTRIBUTE_UNUSED) |
315 | { |
316 | gcov_position_t pos = gcov_position (); |
317 | |
318 | if (!length) |
319 | printf (format: " placeholder" ); |
320 | else |
321 | { |
322 | printf (format: " ident=%u" , gcov_read_unsigned ()); |
323 | printf (format: ", lineno_checksum=0x%08x" , gcov_read_unsigned ()); |
324 | printf (format: ", cfg_checksum=0x%08x" , gcov_read_unsigned ()); |
325 | |
326 | if (gcov_position () - pos < (gcov_position_t) length) |
327 | { |
328 | const char *name; |
329 | |
330 | name = gcov_read_string (); |
331 | printf (format: ", `%s'" , name ? name : "NULL" ); |
332 | unsigned artificial = gcov_read_unsigned (); |
333 | name = gcov_read_string (); |
334 | printf (format: " %s" , name ? name : "NULL" ); |
335 | unsigned line_start = gcov_read_unsigned (); |
336 | unsigned column_start = gcov_read_unsigned (); |
337 | unsigned line_end = gcov_read_unsigned (); |
338 | unsigned column_end = gcov_read_unsigned (); |
339 | printf (format: ":%u:%u-%u:%u" , line_start, column_start, |
340 | line_end, column_end); |
341 | if (artificial) |
342 | printf (format: ", artificial" ); |
343 | } |
344 | } |
345 | } |
346 | |
347 | static void |
348 | tag_blocks (const char *filename ATTRIBUTE_UNUSED, |
349 | unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED, |
350 | unsigned depth ATTRIBUTE_UNUSED) |
351 | { |
352 | printf (format: " %u blocks" , gcov_read_unsigned ()); |
353 | } |
354 | |
355 | static void |
356 | tag_arcs (const char *filename ATTRIBUTE_UNUSED, |
357 | unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED, |
358 | unsigned depth) |
359 | { |
360 | unsigned n_arcs = GCOV_TAG_ARCS_NUM (length); |
361 | |
362 | printf (format: " %u arcs" , n_arcs); |
363 | if (flag_dump_contents) |
364 | { |
365 | unsigned ix; |
366 | unsigned blockno = gcov_read_unsigned (); |
367 | |
368 | for (ix = 0; ix != n_arcs; ix++) |
369 | { |
370 | unsigned dst, flags; |
371 | |
372 | if (!(ix & 3)) |
373 | { |
374 | printf (format: "\n" ); |
375 | print_prefix (filename, depth, position: gcov_position ()); |
376 | printf (VALUE_PADDING_PREFIX "block %u:" , blockno); |
377 | } |
378 | dst = gcov_read_unsigned (); |
379 | flags = gcov_read_unsigned (); |
380 | printf (format: " %u:%04x" , dst, flags); |
381 | if (flags) |
382 | { |
383 | char c = '('; |
384 | |
385 | if (flags & GCOV_ARC_ON_TREE) |
386 | printf (format: "%ctree" , c), c = ','; |
387 | if (flags & GCOV_ARC_FAKE) |
388 | printf (format: "%cfake" , c), c = ','; |
389 | if (flags & GCOV_ARC_FALLTHROUGH) |
390 | printf (format: "%cfall" , c), c = ','; |
391 | printf (format: ")" ); |
392 | } |
393 | } |
394 | } |
395 | } |
396 | |
397 | /* Print number of conditions (not outcomes, i.e. if (x && y) is 2, not 4). */ |
398 | static void |
399 | tag_conditions (const char *filename, unsigned /* tag */, int length, |
400 | unsigned depth) |
401 | { |
402 | unsigned n_conditions = GCOV_TAG_CONDS_NUM (length); |
403 | |
404 | printf (format: " %u conditions" , n_conditions); |
405 | if (flag_dump_contents) |
406 | { |
407 | for (unsigned ix = 0; ix != n_conditions; ix++) |
408 | { |
409 | const unsigned blockno = gcov_read_unsigned (); |
410 | const unsigned nterms = gcov_read_unsigned (); |
411 | |
412 | printf (format: "\n" ); |
413 | print_prefix (filename, depth, position: gcov_position ()); |
414 | printf (VALUE_PADDING_PREFIX "block %u:" , blockno); |
415 | printf (format: " %u" , nterms); |
416 | } |
417 | } |
418 | } |
419 | static void |
420 | tag_lines (const char *filename ATTRIBUTE_UNUSED, |
421 | unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED, |
422 | unsigned depth) |
423 | { |
424 | if (flag_dump_contents) |
425 | { |
426 | unsigned blockno = gcov_read_unsigned (); |
427 | char const *sep = NULL; |
428 | |
429 | while (1) |
430 | { |
431 | gcov_position_t position = gcov_position (); |
432 | const char *source = NULL; |
433 | unsigned lineno = gcov_read_unsigned (); |
434 | |
435 | if (!lineno) |
436 | { |
437 | source = gcov_read_string (); |
438 | if (!source) |
439 | break; |
440 | sep = NULL; |
441 | } |
442 | |
443 | if (!sep) |
444 | { |
445 | printf (format: "\n" ); |
446 | print_prefix (filename, depth, position); |
447 | printf (VALUE_PADDING_PREFIX "block %u:" , blockno); |
448 | sep = "" ; |
449 | } |
450 | if (lineno) |
451 | { |
452 | printf (format: "%s%u" , sep, lineno); |
453 | sep = ", " ; |
454 | } |
455 | else |
456 | { |
457 | printf (format: "%s`%s'" , sep, source); |
458 | sep = ":" ; |
459 | } |
460 | } |
461 | } |
462 | } |
463 | |
464 | static void |
465 | tag_counters (const char *filename ATTRIBUTE_UNUSED, |
466 | unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED, |
467 | unsigned depth) |
468 | { |
469 | #define DEF_GCOV_COUNTER(COUNTER, NAME, MERGE_FN) NAME, |
470 | static const char *const counter_names[] = { |
471 | #include "gcov-counter.def" |
472 | }; |
473 | #undef DEF_GCOV_COUNTER |
474 | int n_counts = GCOV_TAG_COUNTER_NUM (length); |
475 | bool has_zeros = n_counts < 0; |
476 | n_counts = abs (x: n_counts); |
477 | unsigned counter_idx = GCOV_COUNTER_FOR_TAG (tag); |
478 | |
479 | printf (format: " %s %u counts%s" , |
480 | counter_names[counter_idx], n_counts, |
481 | has_zeros ? " (all zero)" : "" ); |
482 | if (flag_dump_contents) |
483 | { |
484 | vector<gcov_type> counters; |
485 | for (int ix = 0; ix != n_counts; ix++) |
486 | counters.push_back (x: has_zeros ? 0 : gcov_read_counter ()); |
487 | |
488 | /* Make stable sort for TOP N counters. */ |
489 | if (flag_dump_stable) |
490 | if (counter_idx == GCOV_COUNTER_V_INDIR |
491 | || counter_idx == GCOV_COUNTER_V_TOPN) |
492 | { |
493 | unsigned start = 0; |
494 | while (start < counters.size ()) |
495 | { |
496 | unsigned n = counters[start + 1]; |
497 | |
498 | /* Use bubble sort. */ |
499 | for (unsigned i = 1; i <= n; ++i) |
500 | for (unsigned j = i; j <= n; ++j) |
501 | { |
502 | gcov_type key1 = counters[start + 2 * i]; |
503 | gcov_type value1 = counters[start + 2 * i + 1]; |
504 | gcov_type key2 = counters[start + 2 * j]; |
505 | gcov_type value2 = counters[start + 2 * j + 1]; |
506 | |
507 | if (value1 < value2 || (value1 == value2 && key1 < key2)) |
508 | { |
509 | std::swap (a&: counters[start + 2 * i], |
510 | b&: counters[start + 2 * j]); |
511 | std::swap (a&: counters[start + 2 * i + 1], |
512 | b&: counters[start + 2 * j + 1]); |
513 | } |
514 | } |
515 | start += 2 * (n + 1); |
516 | } |
517 | if (start != counters.size ()) |
518 | abort (); |
519 | } |
520 | |
521 | for (unsigned ix = 0; ix < counters.size (); ++ix) |
522 | { |
523 | if (flag_dump_raw) |
524 | { |
525 | if (ix == 0) |
526 | printf (format: ": " ); |
527 | } |
528 | else if (!(ix & 7)) |
529 | { |
530 | printf (format: "\n" ); |
531 | print_prefix (filename, depth, position: gcov_position ()); |
532 | printf (VALUE_PADDING_PREFIX VALUE_PREFIX, ix); |
533 | } |
534 | |
535 | printf (format: "%" PRId64 " " , counters[ix]); |
536 | } |
537 | } |
538 | } |
539 | |
540 | static void |
541 | tag_summary (const char *filename ATTRIBUTE_UNUSED, |
542 | unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED, |
543 | unsigned depth ATTRIBUTE_UNUSED) |
544 | { |
545 | gcov_summary summary; |
546 | gcov_read_summary (summary: &summary); |
547 | printf (format: " runs=%d, sum_max=%" PRId64, |
548 | summary.runs, summary.sum_max); |
549 | } |
550 | |