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