1 | /* Routines required for instrumenting a program. */ |
2 | /* Compile this one with gcc. */ |
3 | /* Copyright (C) 1989-2024 Free Software Foundation, Inc. |
4 | |
5 | This file is part of GCC. |
6 | |
7 | GCC is free software; you can redistribute it and/or modify it under |
8 | the terms of the GNU General Public License as published by the Free |
9 | Software Foundation; either version 3, or (at your option) any later |
10 | version. |
11 | |
12 | GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
13 | WARRANTY; without even the implied warranty of MERCHANTABILITY or |
14 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
15 | for more details. |
16 | |
17 | Under Section 7 of GPL version 3, you are granted additional |
18 | permissions described in the GCC Runtime Library Exception, version |
19 | 3.1, as published by the Free Software Foundation. |
20 | |
21 | You should have received a copy of the GNU General Public License and |
22 | a copy of the GCC Runtime Library Exception along with this program; |
23 | see the files COPYING3 and COPYING.RUNTIME respectively. If not, see |
24 | <http://www.gnu.org/licenses/>. */ |
25 | |
26 | #include "libgcov.h" |
27 | #include "gcov-io.h" |
28 | |
29 | /* Return 1, if all counter values are zero, otherwise 0. */ |
30 | |
31 | static inline int |
32 | are_all_counters_zero (const struct gcov_ctr_info *ci_ptr) |
33 | { |
34 | for (unsigned i = 0; i < ci_ptr->num; i++) |
35 | if (ci_ptr->values[i] != 0) |
36 | return 0; |
37 | |
38 | return 1; |
39 | } |
40 | |
41 | #if defined(inhibit_libc) |
42 | /* If libc and its header files are not available, provide dummy functions. */ |
43 | |
44 | #if defined(L_gcov) |
45 | void __gcov_init (struct gcov_info *p __attribute__ ((unused))) {} |
46 | #endif |
47 | |
48 | #else /* inhibit_libc */ |
49 | |
50 | #if GCOV_LOCKED |
51 | #include <fcntl.h> |
52 | #include <errno.h> |
53 | #include <sys/stat.h> |
54 | #elif GCOV_LOCKED_WITH_LOCKING |
55 | #include <fcntl.h> |
56 | #include <sys/locking.h> |
57 | #include <sys/stat.h> |
58 | #endif |
59 | |
60 | #if HAVE_SYS_MMAN_H |
61 | #include <sys/mman.h> |
62 | #endif |
63 | |
64 | #endif /* inhibit_libc */ |
65 | |
66 | #if defined(L_gcov) && !defined(inhibit_libc) |
67 | #define NEED_L_GCOV |
68 | #endif |
69 | |
70 | #if defined(L_gcov_info_to_gcda) && !IN_GCOV_TOOL |
71 | #define NEED_L_GCOV_INFO_TO_GCDA |
72 | #endif |
73 | |
74 | #ifdef NEED_L_GCOV |
75 | /* A utility function for outputting errors. */ |
76 | static int gcov_error (const char *, ...); |
77 | |
78 | #if !IN_GCOV_TOOL |
79 | static void gcov_error_exit (void); |
80 | #endif |
81 | |
82 | #include "gcov-io.cc" |
83 | |
84 | #define GCOV_PROF_PREFIX "libgcov profiling error:%s:" |
85 | |
86 | struct gcov_fn_buffer |
87 | { |
88 | struct gcov_fn_buffer *next; |
89 | unsigned fn_ix; |
90 | struct gcov_fn_info info; |
91 | /* note gcov_fn_info ends in a trailing array. */ |
92 | }; |
93 | |
94 | struct gcov_summary_buffer |
95 | { |
96 | struct gcov_summary_buffer *next; |
97 | struct gcov_summary summary; |
98 | }; |
99 | |
100 | /* A struct that bundles all the related information about the |
101 | gcda filename. */ |
102 | |
103 | struct gcov_filename |
104 | { |
105 | char *filename; /* filename buffer */ |
106 | int strip; /* leading chars to strip from filename */ |
107 | char *prefix; /* prefix string */ |
108 | }; |
109 | |
110 | static struct gcov_fn_buffer * |
111 | free_fn_data (const struct gcov_info *gi_ptr, struct gcov_fn_buffer *buffer, |
112 | unsigned limit) |
113 | { |
114 | struct gcov_fn_buffer *next; |
115 | unsigned ix, n_ctr = 0; |
116 | |
117 | if (!buffer) |
118 | return 0; |
119 | next = buffer->next; |
120 | |
121 | for (ix = 0; ix != limit; ix++) |
122 | if (gi_ptr->merge[ix]) |
123 | free (ptr: buffer->info.ctrs[n_ctr++].values); |
124 | free (ptr: buffer); |
125 | return next; |
126 | } |
127 | |
128 | static struct gcov_fn_buffer ** |
129 | buffer_fn_data (const char *filename, const struct gcov_info *gi_ptr, |
130 | struct gcov_fn_buffer **end_ptr, unsigned fn_ix) |
131 | { |
132 | unsigned n_ctrs = 0, ix = 0; |
133 | struct gcov_fn_buffer *fn_buffer; |
134 | unsigned len; |
135 | |
136 | for (ix = GCOV_COUNTERS; ix--;) |
137 | if (gi_ptr->merge[ix]) |
138 | n_ctrs++; |
139 | |
140 | len = sizeof (*fn_buffer) + sizeof (fn_buffer->info.ctrs[0]) * n_ctrs; |
141 | fn_buffer = (struct gcov_fn_buffer *) xmalloc (len); |
142 | |
143 | if (!fn_buffer) |
144 | goto fail; |
145 | |
146 | fn_buffer->next = 0; |
147 | fn_buffer->fn_ix = fn_ix; |
148 | fn_buffer->info.ident = gcov_read_unsigned (); |
149 | fn_buffer->info.lineno_checksum = gcov_read_unsigned (); |
150 | fn_buffer->info.cfg_checksum = gcov_read_unsigned (); |
151 | |
152 | for (n_ctrs = ix = 0; ix != GCOV_COUNTERS; ix++) |
153 | { |
154 | gcov_unsigned_t length; |
155 | gcov_type *values; |
156 | |
157 | if (!gi_ptr->merge[ix]) |
158 | continue; |
159 | |
160 | if (gcov_read_unsigned () != GCOV_TAG_FOR_COUNTER (ix)) |
161 | { |
162 | len = 0; |
163 | goto fail; |
164 | } |
165 | |
166 | length = GCOV_TAG_COUNTER_NUM (gcov_read_unsigned ()); |
167 | len = length * sizeof (gcov_type); |
168 | values = (gcov_type *) xmalloc (len); |
169 | if (!values) |
170 | goto fail; |
171 | |
172 | fn_buffer->info.ctrs[n_ctrs].num = length; |
173 | fn_buffer->info.ctrs[n_ctrs].values = values; |
174 | |
175 | while (length--) |
176 | *values++ = gcov_read_counter (); |
177 | n_ctrs++; |
178 | } |
179 | |
180 | *end_ptr = fn_buffer; |
181 | return &fn_buffer->next; |
182 | |
183 | fail: |
184 | gcov_error (GCOV_PROF_PREFIX "Function %u %s %u \n" , filename, fn_ix, |
185 | len ? "cannot allocate" : "counter mismatch" , len ? len : ix); |
186 | |
187 | return (struct gcov_fn_buffer **)free_fn_data (gi_ptr, buffer: fn_buffer, limit: ix); |
188 | } |
189 | |
190 | /* Convert VERSION into a string description and return the it. |
191 | BUFFER is used for storage of the string. The code should be |
192 | aligned wit gcov-iov.c. */ |
193 | |
194 | static char * |
195 | gcov_version_string (char *buffer, char version[4]) |
196 | { |
197 | if (version[0] < 'A' || version[0] > 'Z' |
198 | || version[1] < '0' || version[1] > '9' |
199 | || version[2] < '0' || version[2] > '9') |
200 | sprintf (s: buffer, format: "(unknown)" ); |
201 | else |
202 | { |
203 | unsigned major = 10 * (version[0] - 'A') + (version[1] - '0'); |
204 | unsigned minor = version[2] - '0'; |
205 | sprintf (s: buffer, format: "%u.%u (%s)" , major, minor, |
206 | version[3] == '*' ? "release" : "experimental" ); |
207 | } |
208 | return buffer; |
209 | } |
210 | |
211 | /* Check if VERSION of the info block PTR matches libgcov one. |
212 | Return 1 on success, or zero in case of versions mismatch. |
213 | If FILENAME is not NULL, its value used for reporting purposes |
214 | instead of value from the info block. */ |
215 | |
216 | static int |
217 | gcov_version (struct gcov_info *ptr, gcov_unsigned_t version, |
218 | const char *filename) |
219 | { |
220 | if (version != GCOV_VERSION) |
221 | { |
222 | char v[4], e[4]; |
223 | char ver_string[128], expected_string[128]; |
224 | |
225 | GCOV_UNSIGNED2STRING (v, version); |
226 | GCOV_UNSIGNED2STRING (e, GCOV_VERSION); |
227 | |
228 | gcov_error (GCOV_PROF_PREFIX "Version mismatch - expected %s (%.4s) " |
229 | "got %s (%.4s)\n" , |
230 | filename? filename : ptr->filename, |
231 | gcov_version_string (buffer: expected_string, version: e), e, |
232 | gcov_version_string (buffer: ver_string, version: v), v); |
233 | return 0; |
234 | } |
235 | return 1; |
236 | } |
237 | |
238 | /* buffer for the fn_data from another program. */ |
239 | static struct gcov_fn_buffer *fn_buffer; |
240 | |
241 | /* Including system dependent components. */ |
242 | #include "libgcov-driver-system.c" |
243 | |
244 | /* This function merges counters in GI_PTR to an existing gcda file. |
245 | Return 0 on success. |
246 | Return -1 on error. In this case, caller will goto read_fatal. */ |
247 | |
248 | static int |
249 | merge_one_data (const char *filename, |
250 | struct gcov_info *gi_ptr, |
251 | struct gcov_summary *summary) |
252 | { |
253 | gcov_unsigned_t tag, length; |
254 | unsigned t_ix; |
255 | int f_ix = -1; |
256 | int error = 0; |
257 | struct gcov_fn_buffer **fn_tail = &fn_buffer; |
258 | |
259 | length = gcov_read_unsigned (); |
260 | if (!gcov_version (ptr: gi_ptr, version: length, filename)) |
261 | return -1; |
262 | |
263 | /* Skip timestamp. */ |
264 | gcov_read_unsigned (); |
265 | |
266 | length = gcov_read_unsigned (); |
267 | if (length != gi_ptr->checksum) |
268 | { |
269 | /* Read from a different compilation. Overwrite the file. */ |
270 | gcov_error (GCOV_PROF_PREFIX "overwriting an existing profile data " |
271 | "with a different checksum\n" , filename); |
272 | return 0; |
273 | } |
274 | |
275 | tag = gcov_read_unsigned (); |
276 | if (tag != GCOV_TAG_OBJECT_SUMMARY) |
277 | goto read_mismatch; |
278 | length = gcov_read_unsigned (); |
279 | gcc_assert (length > 0); |
280 | gcov_read_summary (summary); |
281 | |
282 | tag = gcov_read_unsigned (); |
283 | /* Merge execution counts for each function. */ |
284 | for (f_ix = 0; (unsigned)f_ix != gi_ptr->n_functions; |
285 | f_ix++, tag = gcov_read_unsigned ()) |
286 | { |
287 | const struct gcov_ctr_info *ci_ptr; |
288 | const struct gcov_fn_info *gfi_ptr = gi_ptr->functions[f_ix]; |
289 | |
290 | if (tag != GCOV_TAG_FUNCTION) |
291 | goto read_mismatch; |
292 | |
293 | length = gcov_read_unsigned (); |
294 | if (!length) |
295 | /* This function did not appear in the other program. |
296 | We have nothing to merge. */ |
297 | continue; |
298 | |
299 | if (length != GCOV_TAG_FUNCTION_LENGTH) |
300 | goto read_mismatch; |
301 | |
302 | if (!gfi_ptr || gfi_ptr->key != gi_ptr) |
303 | { |
304 | /* This function appears in the other program. We |
305 | need to buffer the information in order to write |
306 | it back out -- we'll be inserting data before |
307 | this point, so cannot simply keep the data in the |
308 | file. */ |
309 | fn_tail = buffer_fn_data (filename, gi_ptr, end_ptr: fn_tail, fn_ix: f_ix); |
310 | if (!fn_tail) |
311 | goto read_mismatch; |
312 | continue; |
313 | } |
314 | |
315 | length = gcov_read_unsigned (); |
316 | if (length != gfi_ptr->ident) |
317 | goto read_mismatch; |
318 | |
319 | length = gcov_read_unsigned (); |
320 | if (length != gfi_ptr->lineno_checksum) |
321 | goto read_mismatch; |
322 | |
323 | length = gcov_read_unsigned (); |
324 | if (length != gfi_ptr->cfg_checksum) |
325 | goto read_mismatch; |
326 | |
327 | ci_ptr = gfi_ptr->ctrs; |
328 | for (t_ix = 0; t_ix < GCOV_COUNTERS; t_ix++) |
329 | { |
330 | gcov_merge_fn merge = gi_ptr->merge[t_ix]; |
331 | |
332 | if (!merge) |
333 | continue; |
334 | |
335 | tag = gcov_read_unsigned (); |
336 | int read_length = (int)gcov_read_unsigned (); |
337 | length = abs (x: read_length); |
338 | if (tag != GCOV_TAG_FOR_COUNTER (t_ix) |
339 | || (length != GCOV_TAG_COUNTER_LENGTH (ci_ptr->num) |
340 | && t_ix != GCOV_COUNTER_V_TOPN |
341 | && t_ix != GCOV_COUNTER_V_INDIR)) |
342 | goto read_mismatch; |
343 | /* Merging with all zero counters does not make sense. */ |
344 | if (read_length > 0) |
345 | (*merge) (ci_ptr->values, ci_ptr->num); |
346 | ci_ptr++; |
347 | } |
348 | if ((error = gcov_is_error ())) |
349 | goto read_error; |
350 | } |
351 | |
352 | if (tag) |
353 | { |
354 | read_mismatch:; |
355 | gcov_error (GCOV_PROF_PREFIX "Merge mismatch for %s %u\n" , |
356 | filename, f_ix >= 0 ? "function" : "summary" , |
357 | f_ix < 0 ? -1 - f_ix : f_ix); |
358 | return -1; |
359 | } |
360 | return 0; |
361 | |
362 | read_error: |
363 | gcov_error (GCOV_PROF_PREFIX "%s merging\n" , filename, |
364 | error < 0 ? "Overflow" : "Error" ); |
365 | return -1; |
366 | } |
367 | |
368 | /* Write the DATA of LENGTH characters to the gcov file. */ |
369 | |
370 | static void |
371 | gcov_dump_handler (const void *data, |
372 | unsigned length, |
373 | void *arg ATTRIBUTE_UNUSED) |
374 | { |
375 | gcov_write (data, length); |
376 | } |
377 | |
378 | /* Allocate SIZE characters and return the address of the allocated memory. */ |
379 | |
380 | static void * |
381 | gcov_allocate_handler (unsigned size, void *arg ATTRIBUTE_UNUSED) |
382 | { |
383 | return xmalloc (size); |
384 | } |
385 | #endif /* NEED_L_GCOV */ |
386 | |
387 | #if defined(NEED_L_GCOV) || defined(NEED_L_GCOV_INFO_TO_GCDA) |
388 | /* Dump the WORD using the DUMP handler called with ARG. */ |
389 | |
390 | static inline void |
391 | dump_unsigned (gcov_unsigned_t word, |
392 | void (*dump_fn) (const void *, unsigned, void *), |
393 | void *arg) |
394 | { |
395 | (*dump_fn) (&word, sizeof (word), arg); |
396 | } |
397 | |
398 | /* Dump the COUNTER using the DUMP handler called with ARG. */ |
399 | |
400 | static inline void |
401 | dump_counter (gcov_type counter, |
402 | void (*dump_fn) (const void *, unsigned, void *), |
403 | void *arg) |
404 | { |
405 | dump_unsigned (word: (gcov_unsigned_t)counter, dump_fn, arg); |
406 | |
407 | if (sizeof (counter) > sizeof (gcov_unsigned_t)) |
408 | dump_unsigned (word: (gcov_unsigned_t)(counter >> 32), dump_fn, arg); |
409 | else |
410 | dump_unsigned (word: 0, dump_fn, arg); |
411 | } |
412 | |
413 | /* Dump the STRING using the DUMP handler called with ARG. */ |
414 | |
415 | static inline void |
416 | ATTRIBUTE_UNUSED |
417 | dump_string (const char *string, |
418 | void (*dump_fn) (const void *, unsigned, void *), |
419 | void *arg) |
420 | { |
421 | unsigned length = 0; |
422 | |
423 | if (string) |
424 | length = strlen (s: string) + 1; |
425 | |
426 | dump_unsigned (word: length, dump_fn, arg); |
427 | if (string) |
428 | (*dump_fn) (string, length, arg); |
429 | } |
430 | |
431 | #define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) |
432 | |
433 | /* Store all TOP N counters where each has a dynamic length. */ |
434 | |
435 | static void |
436 | write_topn_counters (const struct gcov_ctr_info *ci_ptr, |
437 | unsigned t_ix, |
438 | gcov_unsigned_t n_counts, |
439 | void (*dump_fn) (const void *, unsigned, void *), |
440 | void *(*allocate_fn)(unsigned, void *), |
441 | void *arg) |
442 | { |
443 | unsigned counters = n_counts / GCOV_TOPN_MEM_COUNTERS; |
444 | gcc_assert (n_counts % GCOV_TOPN_MEM_COUNTERS == 0); |
445 | |
446 | /* It can happen in a multi-threaded environment that number of counters is |
447 | different from the size of the corresponding linked lists. */ |
448 | #define LIST_SIZE_MIN_LENGTH 4 * 1024 |
449 | |
450 | static unsigned *list_sizes = NULL; |
451 | static unsigned list_size_length = 0; |
452 | |
453 | if (list_sizes == NULL || counters > list_size_length) |
454 | { |
455 | list_size_length = MAX (LIST_SIZE_MIN_LENGTH, 2 * counters); |
456 | #if !defined(inhibit_libc) && HAVE_SYS_MMAN_H |
457 | list_sizes |
458 | = (unsigned *)malloc_mmap (length: list_size_length * sizeof (unsigned)); |
459 | #endif |
460 | |
461 | /* Malloc fallback. */ |
462 | if (list_sizes == NULL) |
463 | list_sizes = |
464 | (unsigned *)(*allocate_fn) (list_size_length * sizeof (unsigned), |
465 | arg); |
466 | } |
467 | |
468 | unsigned pair_total = 0; |
469 | |
470 | for (unsigned i = 0; i < counters; i++) |
471 | { |
472 | gcov_type start = ci_ptr->values[GCOV_TOPN_MEM_COUNTERS * i + 2]; |
473 | unsigned sizes = 0; |
474 | |
475 | for (struct gcov_kvp *node = (struct gcov_kvp *)(__INTPTR_TYPE__)start; |
476 | node != NULL; node = node->next) |
477 | ++sizes; |
478 | |
479 | pair_total += sizes; |
480 | list_sizes[i] = sizes; |
481 | } |
482 | |
483 | unsigned disk_size = GCOV_TOPN_DISK_COUNTERS * counters + 2 * pair_total; |
484 | dump_unsigned (GCOV_TAG_FOR_COUNTER (t_ix), dump_fn, arg), |
485 | dump_unsigned (GCOV_TAG_COUNTER_LENGTH (disk_size), dump_fn, arg); |
486 | |
487 | for (unsigned i = 0; i < counters; i++) |
488 | { |
489 | dump_counter (counter: ci_ptr->values[GCOV_TOPN_MEM_COUNTERS * i], dump_fn, arg); |
490 | dump_counter (counter: list_sizes[i], dump_fn, arg); |
491 | gcov_type start = ci_ptr->values[GCOV_TOPN_MEM_COUNTERS * i + 2]; |
492 | |
493 | unsigned j = 0; |
494 | for (struct gcov_kvp *node = (struct gcov_kvp *)(__INTPTR_TYPE__)start; |
495 | j < list_sizes[i]; node = node->next, j++) |
496 | { |
497 | dump_counter (counter: node->value, dump_fn, arg); |
498 | dump_counter (counter: node->count, dump_fn, arg); |
499 | } |
500 | } |
501 | } |
502 | |
503 | /* Write counters in GI_PTR and the summary in PRG to a gcda file. In |
504 | the case of appending to an existing file, SUMMARY_POS will be non-zero. |
505 | We will write the file starting from SUMMAY_POS. */ |
506 | |
507 | static void |
508 | write_one_data (const struct gcov_info *gi_ptr, |
509 | const struct gcov_summary *prg_p ATTRIBUTE_UNUSED, |
510 | void (*dump_fn) (const void *, unsigned, void *), |
511 | void *(*allocate_fn) (unsigned, void *), |
512 | void *arg) |
513 | { |
514 | unsigned f_ix; |
515 | |
516 | dump_unsigned (GCOV_DATA_MAGIC, dump_fn, arg); |
517 | dump_unsigned (GCOV_VERSION, dump_fn, arg); |
518 | dump_unsigned (word: gi_ptr->stamp, dump_fn, arg); |
519 | dump_unsigned (word: gi_ptr->checksum, dump_fn, arg); |
520 | |
521 | #ifdef NEED_L_GCOV |
522 | /* Generate whole program statistics. */ |
523 | gcov_write_object_summary (summary: prg_p); |
524 | #endif |
525 | |
526 | /* Write execution counts for each function. */ |
527 | for (f_ix = 0; f_ix != gi_ptr->n_functions; f_ix++) |
528 | { |
529 | #ifdef NEED_L_GCOV |
530 | unsigned buffered = 0; |
531 | #endif |
532 | const struct gcov_fn_info *gfi_ptr; |
533 | const struct gcov_ctr_info *ci_ptr; |
534 | gcov_unsigned_t length; |
535 | unsigned t_ix; |
536 | |
537 | #ifdef NEED_L_GCOV |
538 | if (fn_buffer && fn_buffer->fn_ix == f_ix) |
539 | { |
540 | /* Buffered data from another program. */ |
541 | buffered = 1; |
542 | gfi_ptr = &fn_buffer->info; |
543 | length = GCOV_TAG_FUNCTION_LENGTH; |
544 | } |
545 | else |
546 | #endif |
547 | { |
548 | gfi_ptr = gi_ptr->functions[f_ix]; |
549 | if (gfi_ptr && gfi_ptr->key == gi_ptr) |
550 | length = GCOV_TAG_FUNCTION_LENGTH; |
551 | else |
552 | length = 0; |
553 | } |
554 | |
555 | dump_unsigned (GCOV_TAG_FUNCTION, dump_fn, arg); |
556 | dump_unsigned (word: length, dump_fn, arg); |
557 | if (!length) |
558 | continue; |
559 | |
560 | dump_unsigned (word: gfi_ptr->ident, dump_fn, arg); |
561 | dump_unsigned (word: gfi_ptr->lineno_checksum, dump_fn, arg); |
562 | dump_unsigned (word: gfi_ptr->cfg_checksum, dump_fn, arg); |
563 | |
564 | ci_ptr = gfi_ptr->ctrs; |
565 | for (t_ix = 0; t_ix < GCOV_COUNTERS; t_ix++) |
566 | { |
567 | gcov_position_t n_counts; |
568 | |
569 | if (!gi_ptr->merge[t_ix]) |
570 | continue; |
571 | |
572 | n_counts = ci_ptr->num; |
573 | |
574 | if (t_ix == GCOV_COUNTER_V_TOPN || t_ix == GCOV_COUNTER_V_INDIR) |
575 | write_topn_counters (ci_ptr, t_ix, n_counts, dump_fn, allocate_fn, |
576 | arg); |
577 | else |
578 | { |
579 | dump_unsigned (GCOV_TAG_FOR_COUNTER (t_ix), dump_fn, arg); |
580 | if (are_all_counters_zero (ci_ptr)) |
581 | /* Do not stream when all counters are zero. */ |
582 | dump_unsigned (GCOV_TAG_COUNTER_LENGTH (-n_counts), |
583 | dump_fn, arg); |
584 | else |
585 | { |
586 | dump_unsigned (GCOV_TAG_COUNTER_LENGTH (n_counts), |
587 | dump_fn, arg); |
588 | for (unsigned i = 0; i < n_counts; i++) |
589 | dump_counter (counter: ci_ptr->values[i], dump_fn, arg); |
590 | } |
591 | } |
592 | |
593 | ci_ptr++; |
594 | } |
595 | #ifdef NEED_L_GCOV |
596 | if (buffered) |
597 | fn_buffer = free_fn_data (gi_ptr, buffer: fn_buffer, limit: GCOV_COUNTERS); |
598 | #endif |
599 | } |
600 | |
601 | dump_unsigned (word: 0, dump_fn, arg); |
602 | } |
603 | #endif /* NEED_L_GCOV || NEED_L_GCOV_INFO_TO_GCDA */ |
604 | |
605 | #ifdef NEED_L_GCOV |
606 | /* Dump the coverage counts for one gcov_info object. We merge with existing |
607 | counts when possible, to avoid growing the .da files ad infinitum. We use |
608 | this program's checksum to make sure we only accumulate whole program |
609 | statistics to the correct summary. An object file might be embedded |
610 | in two separate programs, and we must keep the two program |
611 | summaries separate. */ |
612 | |
613 | static void |
614 | dump_one_gcov (struct gcov_info *gi_ptr, struct gcov_filename *gf, |
615 | unsigned run_counted ATTRIBUTE_UNUSED, |
616 | gcov_type run_max ATTRIBUTE_UNUSED, int mode) |
617 | { |
618 | struct gcov_summary summary = {}; |
619 | int error; |
620 | gcov_unsigned_t tag; |
621 | fn_buffer = 0; |
622 | |
623 | error = gcov_exit_open_gcda_file (gi_ptr, gf, mode); |
624 | if (error == -1) |
625 | return; |
626 | |
627 | tag = gcov_read_unsigned (); |
628 | if (tag) |
629 | { |
630 | /* Merge data from file. */ |
631 | if (tag != GCOV_DATA_MAGIC) |
632 | { |
633 | gcov_error (GCOV_PROF_PREFIX "Not a gcov data file\n" , |
634 | gf->filename); |
635 | goto read_fatal; |
636 | } |
637 | error = merge_one_data (filename: gf->filename, gi_ptr, summary: &summary); |
638 | if (error == -1) |
639 | goto read_fatal; |
640 | } |
641 | |
642 | gcov_rewrite (); |
643 | |
644 | #if !IN_GCOV_TOOL |
645 | if (!run_counted) |
646 | { |
647 | summary.runs++; |
648 | summary.sum_max += run_max; |
649 | } |
650 | #else |
651 | summary = gi_ptr->summary; |
652 | #endif |
653 | |
654 | write_one_data (gi_ptr, prg_p: &summary, dump_fn: gcov_dump_handler, allocate_fn: gcov_allocate_handler, |
655 | NULL); |
656 | /* fall through */ |
657 | |
658 | read_fatal:; |
659 | while (fn_buffer) |
660 | fn_buffer = free_fn_data (gi_ptr, buffer: fn_buffer, limit: GCOV_COUNTERS); |
661 | |
662 | if ((error = gcov_close ())) |
663 | gcov_error (fmt: (error < 0 ? GCOV_PROF_PREFIX "Overflow writing\n" |
664 | : GCOV_PROF_PREFIX "Error writing\n" ), gf->filename); |
665 | } |
666 | |
667 | |
668 | /* Dump all the coverage counts for the program. It first computes program |
669 | summary and then traverses gcov_list list and dumps the gcov_info |
670 | objects one by one. Use MODE to open files. */ |
671 | |
672 | #if !IN_GCOV_TOOL |
673 | static |
674 | #endif |
675 | void |
676 | gcov_do_dump (struct gcov_info *list, int run_counted, int mode) |
677 | { |
678 | struct gcov_info *gi_ptr; |
679 | struct gcov_filename gf; |
680 | |
681 | /* Compute run_max of this program run. */ |
682 | gcov_type run_max = 0; |
683 | for (gi_ptr = list; gi_ptr; gi_ptr = gi_ptr->next) |
684 | for (unsigned f_ix = 0; (unsigned)f_ix != gi_ptr->n_functions; f_ix++) |
685 | { |
686 | const struct gcov_ctr_info *cinfo |
687 | = &gi_ptr->functions[f_ix]->ctrs[GCOV_COUNTER_ARCS]; |
688 | |
689 | for (unsigned i = 0; i < cinfo->num; i++) |
690 | if (run_max < cinfo->values[i]) |
691 | run_max = cinfo->values[i]; |
692 | } |
693 | |
694 | allocate_filename_struct (gf: &gf); |
695 | |
696 | /* Now merge each file. */ |
697 | for (gi_ptr = list; gi_ptr; gi_ptr = gi_ptr->next) |
698 | { |
699 | dump_one_gcov (gi_ptr, gf: &gf, run_counted, run_max, mode); |
700 | free (ptr: gf.filename); |
701 | } |
702 | |
703 | free (ptr: gf.prefix); |
704 | } |
705 | |
706 | #if IN_GCOV_TOOL |
707 | const char * |
708 | __attribute__ ((unused)) |
709 | gcov_get_filename (struct gcov_info *list) |
710 | { |
711 | return list->filename; |
712 | } |
713 | #endif |
714 | |
715 | #if !IN_GCOV_TOOL |
716 | void |
717 | __gcov_dump_one (struct gcov_root *root) |
718 | { |
719 | if (root->dumped) |
720 | return; |
721 | |
722 | gcov_do_dump (root->list, root->run_counted, 0); |
723 | |
724 | root->dumped = 1; |
725 | root->run_counted = 1; |
726 | } |
727 | |
728 | /* Per-dynamic-object gcov state. */ |
729 | struct gcov_root __gcov_root; |
730 | |
731 | /* Exactly one of these will be live in the process image. */ |
732 | struct gcov_master __gcov_master = |
733 | {GCOV_VERSION, 0}; |
734 | |
735 | /* Dynamic pool for gcov_kvp structures. */ |
736 | struct gcov_kvp *__gcov_kvp_dynamic_pool; |
737 | |
738 | /* Index into __gcov_kvp_dynamic_pool array. */ |
739 | unsigned __gcov_kvp_dynamic_pool_index; |
740 | |
741 | /* Size of _gcov_kvp_dynamic_pool array. */ |
742 | unsigned __gcov_kvp_dynamic_pool_size; |
743 | |
744 | void |
745 | __gcov_exit (void) |
746 | { |
747 | __gcov_dump_one (&__gcov_root); |
748 | if (__gcov_root.next) |
749 | __gcov_root.next->prev = __gcov_root.prev; |
750 | if (__gcov_root.prev) |
751 | __gcov_root.prev->next = __gcov_root.next; |
752 | else |
753 | __gcov_master.root = __gcov_root.next; |
754 | |
755 | gcov_error_exit (); |
756 | } |
757 | |
758 | /* Add a new object file onto the bb chain. Invoked automatically |
759 | when running an object file's global ctors. */ |
760 | |
761 | void |
762 | __gcov_init (struct gcov_info *info) |
763 | { |
764 | if (!info->version || !info->n_functions) |
765 | return; |
766 | if (gcov_version (info, info->version, 0)) |
767 | { |
768 | if (!__gcov_root.list) |
769 | { |
770 | /* Add to master list and at exit function. */ |
771 | if (gcov_version (NULL, __gcov_master.version, "<master>" )) |
772 | { |
773 | __gcov_root.next = __gcov_master.root; |
774 | if (__gcov_master.root) |
775 | __gcov_master.root->prev = &__gcov_root; |
776 | __gcov_master.root = &__gcov_root; |
777 | } |
778 | } |
779 | |
780 | info->next = __gcov_root.list; |
781 | __gcov_root.list = info; |
782 | } |
783 | } |
784 | #endif /* !IN_GCOV_TOOL */ |
785 | #endif /* NEED_L_GCOV */ |
786 | |
787 | #ifdef NEED_L_GCOV_INFO_TO_GCDA |
788 | /* Convert the gcov info to a gcda data stream. It is intended for |
789 | freestanding environments which do not support the C library file I/O. */ |
790 | |
791 | void |
792 | __gcov_info_to_gcda (const struct gcov_info *gi_ptr, |
793 | void (*filename_fn) (const char *, void *), |
794 | void (*dump_fn) (const void *, unsigned, void *), |
795 | void *(*allocate_fn) (unsigned, void *), |
796 | void *arg) |
797 | { |
798 | (*filename_fn) (gi_ptr->filename, arg); |
799 | write_one_data (gi_ptr, NULL, dump_fn, allocate_fn, arg); |
800 | } |
801 | |
802 | /* Convert the filename to a gcfn data stream. It is intended for |
803 | freestanding environments which do not support the C library file I/O. */ |
804 | |
805 | void |
806 | __gcov_filename_to_gcfn (const char *filename, |
807 | void (*dump_fn) (const void *, unsigned, void *), |
808 | void *arg) |
809 | { |
810 | dump_unsigned (GCOV_FILENAME_MAGIC, dump_fn, arg); |
811 | dump_unsigned (GCOV_VERSION, dump_fn, arg); |
812 | dump_string (filename, dump_fn, arg); |
813 | } |
814 | #endif /* NEED_L_GCOV_INFO_TO_GCDA */ |
815 | |