| /* Routines required for instrumenting a program. */ |
| /* Compile this one with gcc. */ |
| /* Copyright (C) 1989-2021 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it under |
| the terms of the GNU General Public License as published by the Free |
| Software Foundation; either version 3, or (at your option) any later |
| version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
| WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| for more details. |
| |
| Under Section 7 of GPL version 3, you are granted additional |
| permissions described in the GCC Runtime Library Exception, version |
| 3.1, as published by the Free Software Foundation. |
| |
| You should have received a copy of the GNU General Public License and |
| a copy of the GCC Runtime Library Exception along with this program; |
| see the files COPYING3 and COPYING.RUNTIME respectively. If not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "libgcov.h" |
| #include "gcov-io.h" |
| |
| /* Return 1, if all counter values are zero, otherwise 0. */ |
| |
| static inline int |
| are_all_counters_zero (const struct gcov_ctr_info *ci_ptr) |
| { |
| for (unsigned i = 0; i < ci_ptr->num; i++) |
| if (ci_ptr->values[i] != 0) |
| return 0; |
| |
| return 1; |
| } |
| |
| #if defined(inhibit_libc) |
| /* If libc and its header files are not available, provide dummy functions. */ |
| |
| #if defined(L_gcov) |
| void __gcov_init (struct gcov_info *p __attribute__ ((unused))) {} |
| #endif |
| |
| #else /* inhibit_libc */ |
| |
| #if GCOV_LOCKED |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <sys/stat.h> |
| #elif GCOV_LOCKED_WITH_LOCKING |
| #include <fcntl.h> |
| #include <sys/locking.h> |
| #include <sys/stat.h> |
| #endif |
| |
| #if HAVE_SYS_MMAN_H |
| #include <sys/mman.h> |
| #endif |
| |
| #endif /* inhibit_libc */ |
| |
| #if defined(L_gcov) && !defined(inhibit_libc) |
| #define NEED_L_GCOV |
| #endif |
| |
| #if defined(L_gcov_info_to_gcda) && !IN_GCOV_TOOL |
| #define NEED_L_GCOV_INFO_TO_GCDA |
| #endif |
| |
| #ifdef NEED_L_GCOV |
| /* A utility function for outputting errors. */ |
| static int gcov_error (const char *, ...); |
| |
| #if !IN_GCOV_TOOL |
| static void gcov_error_exit (void); |
| #endif |
| |
| #include "gcov-io.c" |
| |
| #define GCOV_PROF_PREFIX "libgcov profiling error:%s:" |
| |
| struct gcov_fn_buffer |
| { |
| struct gcov_fn_buffer *next; |
| unsigned fn_ix; |
| struct gcov_fn_info info; |
| /* note gcov_fn_info ends in a trailing array. */ |
| }; |
| |
| struct gcov_summary_buffer |
| { |
| struct gcov_summary_buffer *next; |
| struct gcov_summary summary; |
| }; |
| |
| /* A struct that bundles all the related information about the |
| gcda filename. */ |
| |
| struct gcov_filename |
| { |
| char *filename; /* filename buffer */ |
| int strip; /* leading chars to strip from filename */ |
| char *prefix; /* prefix string */ |
| }; |
| |
| static struct gcov_fn_buffer * |
| free_fn_data (const struct gcov_info *gi_ptr, struct gcov_fn_buffer *buffer, |
| unsigned limit) |
| { |
| struct gcov_fn_buffer *next; |
| unsigned ix, n_ctr = 0; |
| |
| if (!buffer) |
| return 0; |
| next = buffer->next; |
| |
| for (ix = 0; ix != limit; ix++) |
| if (gi_ptr->merge[ix]) |
| free (buffer->info.ctrs[n_ctr++].values); |
| free (buffer); |
| return next; |
| } |
| |
| static struct gcov_fn_buffer ** |
| buffer_fn_data (const char *filename, const struct gcov_info *gi_ptr, |
| struct gcov_fn_buffer **end_ptr, unsigned fn_ix) |
| { |
| unsigned n_ctrs = 0, ix = 0; |
| struct gcov_fn_buffer *fn_buffer; |
| unsigned len; |
| |
| for (ix = GCOV_COUNTERS; ix--;) |
| if (gi_ptr->merge[ix]) |
| n_ctrs++; |
| |
| len = sizeof (*fn_buffer) + sizeof (fn_buffer->info.ctrs[0]) * n_ctrs; |
| fn_buffer = (struct gcov_fn_buffer *) xmalloc (len); |
| |
| if (!fn_buffer) |
| goto fail; |
| |
| fn_buffer->next = 0; |
| fn_buffer->fn_ix = fn_ix; |
| fn_buffer->info.ident = gcov_read_unsigned (); |
| fn_buffer->info.lineno_checksum = gcov_read_unsigned (); |
| fn_buffer->info.cfg_checksum = gcov_read_unsigned (); |
| |
| for (n_ctrs = ix = 0; ix != GCOV_COUNTERS; ix++) |
| { |
| gcov_unsigned_t length; |
| gcov_type *values; |
| |
| if (!gi_ptr->merge[ix]) |
| continue; |
| |
| if (gcov_read_unsigned () != GCOV_TAG_FOR_COUNTER (ix)) |
| { |
| len = 0; |
| goto fail; |
| } |
| |
| length = GCOV_TAG_COUNTER_NUM (gcov_read_unsigned ()); |
| len = length * sizeof (gcov_type); |
| values = (gcov_type *) xmalloc (len); |
| if (!values) |
| goto fail; |
| |
| fn_buffer->info.ctrs[n_ctrs].num = length; |
| fn_buffer->info.ctrs[n_ctrs].values = values; |
| |
| while (length--) |
| *values++ = gcov_read_counter (); |
| n_ctrs++; |
| } |
| |
| *end_ptr = fn_buffer; |
| return &fn_buffer->next; |
| |
| fail: |
| gcov_error (GCOV_PROF_PREFIX "Function %u %s %u \n", filename, fn_ix, |
| len ? "cannot allocate" : "counter mismatch", len ? len : ix); |
| |
| return (struct gcov_fn_buffer **)free_fn_data (gi_ptr, fn_buffer, ix); |
| } |
| |
| /* Convert VERSION into a string description and return the it. |
| BUFFER is used for storage of the string. The code should be |
| aligned wit gcov-iov.c. */ |
| |
| static char * |
| gcov_version_string (char *buffer, char version[4]) |
| { |
| if (version[0] < 'A' || version[0] > 'Z' |
| || version[1] < '0' || version[1] > '9' |
| || version[2] < '0' || version[2] > '9') |
| sprintf (buffer, "(unknown)"); |
| else |
| { |
| unsigned major = 10 * (version[0] - 'A') + (version[1] - '0'); |
| unsigned minor = version[2] - '0'; |
| sprintf (buffer, "%u.%u (%s)", major, minor, |
| version[3] == '*' ? "release" : "experimental"); |
| } |
| return buffer; |
| } |
| |
| /* Check if VERSION of the info block PTR matches libgcov one. |
| Return 1 on success, or zero in case of versions mismatch. |
| If FILENAME is not NULL, its value used for reporting purposes |
| instead of value from the info block. */ |
| |
| static int |
| gcov_version (struct gcov_info *ptr, gcov_unsigned_t version, |
| const char *filename) |
| { |
| if (version != GCOV_VERSION) |
| { |
| char v[4], e[4]; |
| char ver_string[128], expected_string[128]; |
| |
| GCOV_UNSIGNED2STRING (v, version); |
| GCOV_UNSIGNED2STRING (e, GCOV_VERSION); |
| |
| gcov_error (GCOV_PROF_PREFIX "Version mismatch - expected %s (%.4s) " |
| "got %s (%.4s)\n", |
| filename? filename : ptr->filename, |
| gcov_version_string (expected_string, e), e, |
| gcov_version_string (ver_string, v), v); |
| return 0; |
| } |
| return 1; |
| } |
| |
| /* buffer for the fn_data from another program. */ |
| static struct gcov_fn_buffer *fn_buffer; |
| |
| /* Including system dependent components. */ |
| #include "libgcov-driver-system.c" |
| |
| /* This function merges counters in GI_PTR to an existing gcda file. |
| Return 0 on success. |
| Return -1 on error. In this case, caller will goto read_fatal. */ |
| |
| static int |
| merge_one_data (const char *filename, |
| struct gcov_info *gi_ptr, |
| struct gcov_summary *summary) |
| { |
| gcov_unsigned_t tag, length; |
| unsigned t_ix; |
| int f_ix = -1; |
| int error = 0; |
| struct gcov_fn_buffer **fn_tail = &fn_buffer; |
| |
| length = gcov_read_unsigned (); |
| if (!gcov_version (gi_ptr, length, filename)) |
| return -1; |
| |
| /* Skip timestamp. */ |
| gcov_read_unsigned (); |
| |
| length = gcov_read_unsigned (); |
| if (length != gi_ptr->checksum) |
| { |
| /* Read from a different compilation. Overwrite the file. */ |
| gcov_error (GCOV_PROF_PREFIX "overwriting an existing profile data " |
| "with a different checksum\n", filename); |
| return 0; |
| } |
| |
| tag = gcov_read_unsigned (); |
| if (tag != GCOV_TAG_OBJECT_SUMMARY) |
| goto read_mismatch; |
| length = gcov_read_unsigned (); |
| gcc_assert (length > 0); |
| gcov_read_summary (summary); |
| |
| tag = gcov_read_unsigned (); |
| /* Merge execution counts for each function. */ |
| for (f_ix = 0; (unsigned)f_ix != gi_ptr->n_functions; |
| f_ix++, tag = gcov_read_unsigned ()) |
| { |
| const struct gcov_ctr_info *ci_ptr; |
| const struct gcov_fn_info *gfi_ptr = gi_ptr->functions[f_ix]; |
| |
| if (tag != GCOV_TAG_FUNCTION) |
| goto read_mismatch; |
| |
| length = gcov_read_unsigned (); |
| if (!length) |
| /* This function did not appear in the other program. |
| We have nothing to merge. */ |
| continue; |
| |
| if (length != GCOV_TAG_FUNCTION_LENGTH) |
| goto read_mismatch; |
| |
| if (!gfi_ptr || gfi_ptr->key != gi_ptr) |
| { |
| /* This function appears in the other program. We |
| need to buffer the information in order to write |
| it back out -- we'll be inserting data before |
| this point, so cannot simply keep the data in the |
| file. */ |
| fn_tail = buffer_fn_data (filename, gi_ptr, fn_tail, f_ix); |
| if (!fn_tail) |
| goto read_mismatch; |
| continue; |
| } |
| |
| length = gcov_read_unsigned (); |
| if (length != gfi_ptr->ident) |
| goto read_mismatch; |
| |
| length = gcov_read_unsigned (); |
| if (length != gfi_ptr->lineno_checksum) |
| goto read_mismatch; |
| |
| length = gcov_read_unsigned (); |
| if (length != gfi_ptr->cfg_checksum) |
| goto read_mismatch; |
| |
| ci_ptr = gfi_ptr->ctrs; |
| for (t_ix = 0; t_ix < GCOV_COUNTERS; t_ix++) |
| { |
| gcov_merge_fn merge = gi_ptr->merge[t_ix]; |
| |
| if (!merge) |
| continue; |
| |
| tag = gcov_read_unsigned (); |
| int read_length = (int)gcov_read_unsigned (); |
| length = abs (read_length); |
| if (tag != GCOV_TAG_FOR_COUNTER (t_ix) |
| || (length != GCOV_TAG_COUNTER_LENGTH (ci_ptr->num) |
| && t_ix != GCOV_COUNTER_V_TOPN |
| && t_ix != GCOV_COUNTER_V_INDIR)) |
| goto read_mismatch; |
| /* Merging with all zero counters does not make sense. */ |
| if (read_length > 0) |
| (*merge) (ci_ptr->values, ci_ptr->num); |
| ci_ptr++; |
| } |
| if ((error = gcov_is_error ())) |
| goto read_error; |
| } |
| |
| if (tag) |
| { |
| read_mismatch:; |
| gcov_error (GCOV_PROF_PREFIX "Merge mismatch for %s %u\n", |
| filename, f_ix >= 0 ? "function" : "summary", |
| f_ix < 0 ? -1 - f_ix : f_ix); |
| return -1; |
| } |
| return 0; |
| |
| read_error: |
| gcov_error (GCOV_PROF_PREFIX "%s merging\n", filename, |
| error < 0 ? "Overflow": "Error"); |
| return -1; |
| } |
| |
| /* Write the DATA of LENGTH characters to the gcov file. */ |
| |
| static void |
| gcov_dump_handler (const void *data, |
| unsigned length, |
| void *arg ATTRIBUTE_UNUSED) |
| { |
| gcov_write (data, length); |
| } |
| |
| /* Allocate SIZE characters and return the address of the allocated memory. */ |
| |
| static void * |
| gcov_allocate_handler (unsigned size, void *arg ATTRIBUTE_UNUSED) |
| { |
| return xmalloc (size); |
| } |
| #endif /* NEED_L_GCOV */ |
| |
| #if defined(NEED_L_GCOV) || defined(NEED_L_GCOV_INFO_TO_GCDA) |
| /* Dump the WORD using the DUMP handler called with ARG. */ |
| |
| static inline void |
| dump_unsigned (gcov_unsigned_t word, |
| void (*dump_fn) (const void *, unsigned, void *), |
| void *arg) |
| { |
| (*dump_fn) (&word, sizeof (word), arg); |
| } |
| |
| /* Dump the COUNTER using the DUMP handler called with ARG. */ |
| |
| static inline void |
| dump_counter (gcov_type counter, |
| void (*dump_fn) (const void *, unsigned, void *), |
| void *arg) |
| { |
| dump_unsigned ((gcov_unsigned_t)counter, dump_fn, arg); |
| |
| if (sizeof (counter) > sizeof (gcov_unsigned_t)) |
| dump_unsigned ((gcov_unsigned_t)(counter >> 32), dump_fn, arg); |
| else |
| dump_unsigned (0, dump_fn, arg); |
| } |
| |
| #define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) |
| |
| /* Store all TOP N counters where each has a dynamic length. */ |
| |
| static void |
| write_topn_counters (const struct gcov_ctr_info *ci_ptr, |
| unsigned t_ix, |
| gcov_unsigned_t n_counts, |
| void (*dump_fn) (const void *, unsigned, void *), |
| void *(*allocate_fn)(unsigned, void *), |
| void *arg) |
| { |
| unsigned counters = n_counts / GCOV_TOPN_MEM_COUNTERS; |
| gcc_assert (n_counts % GCOV_TOPN_MEM_COUNTERS == 0); |
| |
| /* It can happen in a multi-threaded environment that number of counters is |
| different from the size of the corresponding linked lists. */ |
| #define LIST_SIZE_MIN_LENGTH 4 * 1024 |
| |
| static unsigned *list_sizes = NULL; |
| static unsigned list_size_length = 0; |
| |
| if (list_sizes == NULL || counters > list_size_length) |
| { |
| list_size_length = MAX (LIST_SIZE_MIN_LENGTH, 2 * counters); |
| #if !defined(inhibit_libc) && HAVE_SYS_MMAN_H |
| list_sizes |
| = (unsigned *)malloc_mmap (list_size_length * sizeof (unsigned)); |
| #endif |
| |
| /* Malloc fallback. */ |
| if (list_sizes == NULL) |
| list_sizes = |
| (unsigned *)(*allocate_fn) (list_size_length * sizeof (unsigned), |
| arg); |
| } |
| |
| unsigned pair_total = 0; |
| |
| for (unsigned i = 0; i < counters; i++) |
| { |
| gcov_type start = ci_ptr->values[GCOV_TOPN_MEM_COUNTERS * i + 2]; |
| unsigned sizes = 0; |
| |
| for (struct gcov_kvp *node = (struct gcov_kvp *)(__INTPTR_TYPE__)start; |
| node != NULL; node = node->next) |
| ++sizes; |
| |
| pair_total += sizes; |
| list_sizes[i] = sizes; |
| } |
| |
| unsigned disk_size = GCOV_TOPN_DISK_COUNTERS * counters + 2 * pair_total; |
| dump_unsigned (GCOV_TAG_FOR_COUNTER (t_ix), dump_fn, arg), |
| dump_unsigned (GCOV_TAG_COUNTER_LENGTH (disk_size), dump_fn, arg); |
| |
| for (unsigned i = 0; i < counters; i++) |
| { |
| dump_counter (ci_ptr->values[GCOV_TOPN_MEM_COUNTERS * i], dump_fn, arg); |
| dump_counter (list_sizes[i], dump_fn, arg); |
| gcov_type start = ci_ptr->values[GCOV_TOPN_MEM_COUNTERS * i + 2]; |
| |
| unsigned j = 0; |
| for (struct gcov_kvp *node = (struct gcov_kvp *)(__INTPTR_TYPE__)start; |
| j < list_sizes[i]; node = node->next, j++) |
| { |
| dump_counter (node->value, dump_fn, arg); |
| dump_counter (node->count, dump_fn, arg); |
| } |
| } |
| } |
| |
| /* Write counters in GI_PTR and the summary in PRG to a gcda file. In |
| the case of appending to an existing file, SUMMARY_POS will be non-zero. |
| We will write the file starting from SUMMAY_POS. */ |
| |
| static void |
| write_one_data (const struct gcov_info *gi_ptr, |
| const struct gcov_summary *prg_p ATTRIBUTE_UNUSED, |
| void (*dump_fn) (const void *, unsigned, void *), |
| void *(*allocate_fn) (unsigned, void *), |
| void *arg) |
| { |
| unsigned f_ix; |
| |
| dump_unsigned (GCOV_DATA_MAGIC, dump_fn, arg); |
| dump_unsigned (GCOV_VERSION, dump_fn, arg); |
| dump_unsigned (gi_ptr->stamp, dump_fn, arg); |
| dump_unsigned (gi_ptr->checksum, dump_fn, arg); |
| |
| #ifdef NEED_L_GCOV |
| /* Generate whole program statistics. */ |
| gcov_write_summary (GCOV_TAG_OBJECT_SUMMARY, prg_p); |
| #endif |
| |
| /* Write execution counts for each function. */ |
| for (f_ix = 0; f_ix != gi_ptr->n_functions; f_ix++) |
| { |
| #ifdef NEED_L_GCOV |
| unsigned buffered = 0; |
| #endif |
| const struct gcov_fn_info *gfi_ptr; |
| const struct gcov_ctr_info *ci_ptr; |
| gcov_unsigned_t length; |
| unsigned t_ix; |
| |
| #ifdef NEED_L_GCOV |
| if (fn_buffer && fn_buffer->fn_ix == f_ix) |
| { |
| /* Buffered data from another program. */ |
| buffered = 1; |
| gfi_ptr = &fn_buffer->info; |
| length = GCOV_TAG_FUNCTION_LENGTH; |
| } |
| else |
| #endif |
| { |
| gfi_ptr = gi_ptr->functions[f_ix]; |
| if (gfi_ptr && gfi_ptr->key == gi_ptr) |
| length = GCOV_TAG_FUNCTION_LENGTH; |
| else |
| length = 0; |
| } |
| |
| dump_unsigned (GCOV_TAG_FUNCTION, dump_fn, arg); |
| dump_unsigned (length, dump_fn, arg); |
| if (!length) |
| continue; |
| |
| dump_unsigned (gfi_ptr->ident, dump_fn, arg); |
| dump_unsigned (gfi_ptr->lineno_checksum, dump_fn, arg); |
| dump_unsigned (gfi_ptr->cfg_checksum, dump_fn, arg); |
| |
| ci_ptr = gfi_ptr->ctrs; |
| for (t_ix = 0; t_ix < GCOV_COUNTERS; t_ix++) |
| { |
| gcov_position_t n_counts; |
| |
| if (!gi_ptr->merge[t_ix]) |
| continue; |
| |
| n_counts = ci_ptr->num; |
| |
| if (t_ix == GCOV_COUNTER_V_TOPN || t_ix == GCOV_COUNTER_V_INDIR) |
| write_topn_counters (ci_ptr, t_ix, n_counts, dump_fn, allocate_fn, |
| arg); |
| else |
| { |
| dump_unsigned (GCOV_TAG_FOR_COUNTER (t_ix), dump_fn, arg); |
| if (are_all_counters_zero (ci_ptr)) |
| /* Do not stream when all counters are zero. */ |
| dump_unsigned (GCOV_TAG_COUNTER_LENGTH (-n_counts), |
| dump_fn, arg); |
| else |
| { |
| dump_unsigned (GCOV_TAG_COUNTER_LENGTH (n_counts), |
| dump_fn, arg); |
| for (unsigned i = 0; i < n_counts; i++) |
| dump_counter (ci_ptr->values[i], dump_fn, arg); |
| } |
| } |
| |
| ci_ptr++; |
| } |
| #ifdef NEED_L_GCOV |
| if (buffered) |
| fn_buffer = free_fn_data (gi_ptr, fn_buffer, GCOV_COUNTERS); |
| #endif |
| } |
| |
| dump_unsigned (0, dump_fn, arg); |
| } |
| #endif /* NEED_L_GCOV || NEED_L_GCOV_INFO_TO_GCDA */ |
| |
| #ifdef NEED_L_GCOV |
| /* Dump the coverage counts for one gcov_info object. We merge with existing |
| counts when possible, to avoid growing the .da files ad infinitum. We use |
| this program's checksum to make sure we only accumulate whole program |
| statistics to the correct summary. An object file might be embedded |
| in two separate programs, and we must keep the two program |
| summaries separate. */ |
| |
| static void |
| dump_one_gcov (struct gcov_info *gi_ptr, struct gcov_filename *gf, |
| unsigned run_counted ATTRIBUTE_UNUSED, |
| gcov_type run_max ATTRIBUTE_UNUSED) |
| { |
| struct gcov_summary summary = {}; |
| int error; |
| gcov_unsigned_t tag; |
| fn_buffer = 0; |
| |
| error = gcov_exit_open_gcda_file (gi_ptr, gf); |
| if (error == -1) |
| return; |
| |
| tag = gcov_read_unsigned (); |
| if (tag) |
| { |
| /* Merge data from file. */ |
| if (tag != GCOV_DATA_MAGIC) |
| { |
| gcov_error (GCOV_PROF_PREFIX "Not a gcov data file\n", |
| gf->filename); |
| goto read_fatal; |
| } |
| error = merge_one_data (gf->filename, gi_ptr, &summary); |
| if (error == -1) |
| goto read_fatal; |
| } |
| |
| gcov_rewrite (); |
| |
| #if !IN_GCOV_TOOL |
| if (!run_counted) |
| { |
| summary.runs++; |
| summary.sum_max += run_max; |
| } |
| #else |
| summary = gi_ptr->summary; |
| #endif |
| |
| write_one_data (gi_ptr, &summary, gcov_dump_handler, gcov_allocate_handler, |
| NULL); |
| /* fall through */ |
| |
| read_fatal:; |
| while (fn_buffer) |
| fn_buffer = free_fn_data (gi_ptr, fn_buffer, GCOV_COUNTERS); |
| |
| if ((error = gcov_close ())) |
| gcov_error ((error < 0 ? GCOV_PROF_PREFIX "Overflow writing\n" |
| : GCOV_PROF_PREFIX "Error writing\n"), gf->filename); |
| } |
| |
| |
| /* Dump all the coverage counts for the program. It first computes program |
| summary and then traverses gcov_list list and dumps the gcov_info |
| objects one by one. */ |
| |
| #if !IN_GCOV_TOOL |
| static |
| #endif |
| void |
| gcov_do_dump (struct gcov_info *list, int run_counted) |
| { |
| struct gcov_info *gi_ptr; |
| struct gcov_filename gf; |
| |
| /* Compute run_max of this program run. */ |
| gcov_type run_max = 0; |
| for (gi_ptr = list; gi_ptr; gi_ptr = gi_ptr->next) |
| for (unsigned f_ix = 0; (unsigned)f_ix != gi_ptr->n_functions; f_ix++) |
| { |
| const struct gcov_ctr_info *cinfo |
| = &gi_ptr->functions[f_ix]->ctrs[GCOV_COUNTER_ARCS]; |
| |
| for (unsigned i = 0; i < cinfo->num; i++) |
| if (run_max < cinfo->values[i]) |
| run_max = cinfo->values[i]; |
| } |
| |
| allocate_filename_struct (&gf); |
| |
| /* Now merge each file. */ |
| for (gi_ptr = list; gi_ptr; gi_ptr = gi_ptr->next) |
| { |
| dump_one_gcov (gi_ptr, &gf, run_counted, run_max); |
| free (gf.filename); |
| } |
| |
| free (gf.prefix); |
| } |
| |
| #if IN_GCOV_TOOL |
| const char * |
| __attribute__ ((unused)) |
| gcov_get_filename (struct gcov_info *list) |
| { |
| return list->filename; |
| } |
| #endif |
| |
| #if !IN_GCOV_TOOL |
| void |
| __gcov_dump_one (struct gcov_root *root) |
| { |
| if (root->dumped) |
| return; |
| |
| gcov_do_dump (root->list, root->run_counted); |
| |
| root->dumped = 1; |
| root->run_counted = 1; |
| } |
| |
| /* Per-dynamic-object gcov state. */ |
| struct gcov_root __gcov_root; |
| |
| /* Exactly one of these will be live in the process image. */ |
| struct gcov_master __gcov_master = |
| {GCOV_VERSION, 0}; |
| |
| /* Dynamic pool for gcov_kvp structures. */ |
| struct gcov_kvp *__gcov_kvp_dynamic_pool; |
| |
| /* Index into __gcov_kvp_dynamic_pool array. */ |
| unsigned __gcov_kvp_dynamic_pool_index; |
| |
| /* Size of _gcov_kvp_dynamic_pool array. */ |
| unsigned __gcov_kvp_dynamic_pool_size; |
| |
| void |
| __gcov_exit (void) |
| { |
| __gcov_dump_one (&__gcov_root); |
| if (__gcov_root.next) |
| __gcov_root.next->prev = __gcov_root.prev; |
| if (__gcov_root.prev) |
| __gcov_root.prev->next = __gcov_root.next; |
| else |
| __gcov_master.root = __gcov_root.next; |
| |
| gcov_error_exit (); |
| } |
| |
| /* Add a new object file onto the bb chain. Invoked automatically |
| when running an object file's global ctors. */ |
| |
| void |
| __gcov_init (struct gcov_info *info) |
| { |
| if (!info->version || !info->n_functions) |
| return; |
| if (gcov_version (info, info->version, 0)) |
| { |
| if (!__gcov_root.list) |
| { |
| /* Add to master list and at exit function. */ |
| if (gcov_version (NULL, __gcov_master.version, "<master>")) |
| { |
| __gcov_root.next = __gcov_master.root; |
| if (__gcov_master.root) |
| __gcov_master.root->prev = &__gcov_root; |
| __gcov_master.root = &__gcov_root; |
| } |
| } |
| |
| info->next = __gcov_root.list; |
| __gcov_root.list = info; |
| } |
| } |
| #endif /* !IN_GCOV_TOOL */ |
| #endif /* NEED_L_GCOV */ |
| |
| #ifdef NEED_L_GCOV_INFO_TO_GCDA |
| /* Convert the gcov info to a gcda data stream. It is intended for |
| free-standing environments which do not support the C library file I/O. */ |
| |
| void |
| __gcov_info_to_gcda (const struct gcov_info *gi_ptr, |
| void (*filename_fn) (const char *, void *), |
| void (*dump_fn) (const void *, unsigned, void *), |
| void *(*allocate_fn) (unsigned, void *), |
| void *arg) |
| { |
| (*filename_fn) (gi_ptr->filename, arg); |
| write_one_data (gi_ptr, NULL, dump_fn, allocate_fn, arg); |
| } |
| #endif /* NEED_L_GCOV_INFO_TO_GCDA */ |