Index: head/sys/conf/files =================================================================== --- head/sys/conf/files +++ head/sys/conf/files @@ -3726,6 +3726,11 @@ fs/cd9660/cd9660_vfsops.c optional cd9660 fs/cd9660/cd9660_vnops.c optional cd9660 fs/cd9660/cd9660_iconv.c optional cd9660_iconv +gnu/gcov/gcc_4_7.c optional gcov +gnu/gcov/gcov_fs.c optional gcov lindebugfs \ + compile-with "${LINUXKPI_C}" +gnu/gcov/gcov_subr.c optional gcov + kern/bus_if.m standard kern/clock_if.m standard kern/cpufreq_if.m standard @@ -4473,6 +4478,12 @@ compat/linuxkpi/common/src/linux_usb.c optional compat_linuxkpi usb \ compile-with "${LINUXKPI_C}" compat/linuxkpi/common/src/linux_work.c optional compat_linuxkpi \ + compile-with "${LINUXKPI_C}" + +compat/linuxkpi/common/src/linux_seq_file.c optional compat_linuxkpi | lindebugfs \ + compile-with "${LINUXKPI_C}" + +compat/lindebugfs/lindebugfs.c optional lindebugfs \ compile-with "${LINUXKPI_C}" # OpenFabrics Enterprise Distribution (Infiniband) Index: head/sys/conf/kern.mk =================================================================== --- head/sys/conf/kern.mk +++ head/sys/conf/kern.mk @@ -56,7 +56,7 @@ -Wno-error=maybe-uninitialized \ -Wno-error=overflow \ -Wno-error=sequence-point \ - -Wno-error=unused-but-set-variable + -Wno-unused-but-set-variable .if ${COMPILER_VERSION} >= 60100 CWARNEXTRA+= -Wno-error=misleading-indentation \ -Wno-error=nonnull-compare \ Index: head/sys/conf/kern.post.mk =================================================================== --- head/sys/conf/kern.post.mk +++ head/sys/conf/kern.post.mk @@ -32,6 +32,10 @@ MKMODULESENV+= SAN_CFLAGS="${SAN_CFLAGS}" .endif +.if defined(GCOV_CFLAGS) +MKMODULESENV+= GCOV_CFLAGS="${GCOV_CFLAGS}" +.endif + # Allow overriding the kernel debug directory, so kernel and user debug may be # installed in different directories. Setting it to "" restores the historical # behavior of installing debug files in the kernel directory. Index: head/sys/conf/kern.pre.mk =================================================================== --- head/sys/conf/kern.pre.mk +++ head/sys/conf/kern.pre.mk @@ -130,6 +130,15 @@ CFLAGS+= ${SAN_CFLAGS} +GCOV_ENABLED!= grep GCOV opt_global.h || true ; echo +.if !empty(GCOV_ENABLED) +.if ${COMPILER_TYPE} == "gcc" +GCOV_CFLAGS+= -fprofile-arcs -ftest-coverage +.endif +.endif + +CFLAGS+= ${GCOV_CFLAGS} + # Put configuration-specific C flags last (except for ${PROF}) so that they # can override the others. CFLAGS+= ${CONF_CFLAGS} Index: head/sys/conf/kmod.mk =================================================================== --- head/sys/conf/kmod.mk +++ head/sys/conf/kmod.mk @@ -380,6 +380,9 @@ # Add the sanitizer C flags CFLAGS+= ${SAN_CFLAGS} +# Add the gcov flags +CFLAGS+= ${GCOV_CFLAGS} + # Respect configuration-specific C flags. CFLAGS+= ${ARCH_FLAGS} ${CONF_CFLAGS} Index: head/sys/conf/options =================================================================== --- head/sys/conf/options +++ head/sys/conf/options @@ -1013,3 +1013,7 @@ # amdsbwd options AMDSBWD_DEBUG opt_amdsbwd.h + +# gcov support +GCOV opt_global.h +LINDEBUGFS Index: head/sys/gnu/gcov/gcc_4_7.c =================================================================== --- head/sys/gnu/gcov/gcc_4_7.c +++ head/sys/gnu/gcov/gcc_4_7.c @@ -0,0 +1,597 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This code provides functions to handle gcc's profiling data format + * introduced with gcc 4.7. + * + * This file is based heavily on gcc_3_4.c file. + * + * For a better understanding, refer to gcc source: + * gcc/gcov-io.h + * libgcc/libgcov.c + * + * Uses gcc-internal data definitions. + */ + + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + + +#if (__GNUC__ >= 7) +#define GCOV_COUNTERS 9 +#elif (__GNUC__ > 5) || (__GNUC__ == 5 && __GNUC_MINOR__ >= 1) +#define GCOV_COUNTERS 10 +#elif __GNUC__ == 4 && __GNUC_MINOR__ >= 9 +#define GCOV_COUNTERS 9 +#else +#define GCOV_COUNTERS 8 +#endif + +#define GCOV_TAG_FUNCTION_LENGTH 3 + +static struct gcov_info *gcov_info_head; + +/** + * struct gcov_ctr_info - information about counters for a single function + * @num: number of counter values for this type + * @values: array of counter values for this type + * + * This data is generated by gcc during compilation and doesn't change + * at run-time with the exception of the values array. + */ +struct gcov_ctr_info { + unsigned int num; + gcov_type *values; +}; + +/** + * struct gcov_fn_info - profiling meta data per function + * @key: comdat key + * @ident: unique ident of function + * @lineno_checksum: function lineo_checksum + * @cfg_checksum: function cfg checksum + * @ctrs: instrumented counters + * + * This data is generated by gcc during compilation and doesn't change + * at run-time. + * + * Information about a single function. This uses the trailing array + * idiom. The number of counters is determined from the merge pointer + * array in gcov_info. The key is used to detect which of a set of + * comdat functions was selected -- it points to the gcov_info object + * of the object file containing the selected comdat function. + */ +struct gcov_fn_info { + const struct gcov_info *key; + unsigned int ident; + unsigned int lineno_checksum; + unsigned int cfg_checksum; + struct gcov_ctr_info ctrs[0]; +}; + +/** + * struct gcov_info - profiling data per object file + * @version: gcov version magic indicating the gcc version used for compilation + * @next: list head for a singly-linked list + * @stamp: uniquifying time stamp + * @filename: name of the associated gcov data file + * @merge: merge functions (null for unused counter type) + * @n_functions: number of instrumented functions + * @functions: pointer to pointers to function information + * + * This data is generated by gcc during compilation and doesn't change + * at run-time with the exception of the next pointer. + */ +struct gcov_info { + unsigned int version; + struct gcov_info *next; + unsigned int stamp; + const char *filename; + void (*merge[GCOV_COUNTERS])(gcov_type *, unsigned int); + unsigned int n_functions; + struct gcov_fn_info **functions; +}; + +/** + * gcov_info_filename - return info filename + * @info: profiling data set + */ +const char * +gcov_info_filename(struct gcov_info *info) +{ + return (info->filename); +} + +/** + * gcov_info_version - return info version + * @info: profiling data set + */ +unsigned int +gcov_info_version(struct gcov_info *info) +{ + return (info->version); +} + +/** + * gcov_info_next - return next profiling data set + * @info: profiling data set + * + * Returns next gcov_info following @info or first gcov_info in the chain if + * @info is %NULL. + */ +struct gcov_info * +gcov_info_next(struct gcov_info *info) +{ + if (!info) + return gcov_info_head; + + return (info->next); +} + +/** + * gcov_info_link - link/add profiling data set to the list + * @info: profiling data set + */ +void +gcov_info_link(struct gcov_info *info) +{ + info->next = gcov_info_head; + gcov_info_head = info; +} + +/** + * gcov_info_unlink - unlink/remove profiling data set from the list + * @prev: previous profiling data set + * @info: profiling data set + */ +void +gcov_info_unlink(struct gcov_info *prev, struct gcov_info *info) +{ + if (prev) + prev->next = info->next; + else + gcov_info_head = info->next; +} + +/* Symbolic links to be created for each profiling data file. */ +const struct gcov_link gcov_link[] = { + { OBJ_TREE, "gcno" }, /* Link to .gcno file in $(objtree). */ + { 0, NULL}, +}; + +/* + * Determine whether a counter is active. Doesn't change at run-time. + */ +static int +counter_active(struct gcov_info *info, unsigned int type) +{ + return (info->merge[type] ? 1 : 0); +} + +/* Determine number of active counters. Based on gcc magic. */ +static unsigned int +num_counter_active(struct gcov_info *info) +{ + unsigned int i; + unsigned int result = 0; + + for (i = 0; i < GCOV_COUNTERS; i++) { + if (counter_active(info, i)) + result++; + } + return (result); +} + +/** + * gcov_info_reset - reset profiling data to zero + * @info: profiling data set + */ +void +gcov_info_reset(struct gcov_info *info) +{ + struct gcov_ctr_info *ci_ptr; + unsigned int fi_idx; + unsigned int ct_idx; + + for (fi_idx = 0; fi_idx < info->n_functions; fi_idx++) { + ci_ptr = info->functions[fi_idx]->ctrs; + + for (ct_idx = 0; ct_idx < GCOV_COUNTERS; ct_idx++) { + if (!counter_active(info, ct_idx)) + continue; + + memset(ci_ptr->values, 0, + sizeof(gcov_type) * ci_ptr->num); + ci_ptr++; + } + } +} + +/** + * gcov_info_is_compatible - check if profiling data can be added + * @info1: first profiling data set + * @info2: second profiling data set + * + * Returns non-zero if profiling data can be added, zero otherwise. + */ +int +gcov_info_is_compatible(struct gcov_info *info1, struct gcov_info *info2) +{ + return (info1->stamp == info2->stamp); +} + +/** + * gcov_info_add - add up profiling data + * @dest: profiling data set to which data is added + * @source: profiling data set which is added + * + * Adds profiling counts of @source to @dest. + */ +void +gcov_info_add(struct gcov_info *dst, struct gcov_info *src) +{ + struct gcov_ctr_info *dci_ptr; + struct gcov_ctr_info *sci_ptr; + unsigned int fi_idx; + unsigned int ct_idx; + unsigned int val_idx; + + for (fi_idx = 0; fi_idx < src->n_functions; fi_idx++) { + dci_ptr = dst->functions[fi_idx]->ctrs; + sci_ptr = src->functions[fi_idx]->ctrs; + + for (ct_idx = 0; ct_idx < GCOV_COUNTERS; ct_idx++) { + if (!counter_active(src, ct_idx)) + continue; + + for (val_idx = 0; val_idx < sci_ptr->num; val_idx++) + dci_ptr->values[val_idx] += + sci_ptr->values[val_idx]; + + dci_ptr++; + sci_ptr++; + } + } +} + +/** + * gcov_info_dup - duplicate profiling data set + * @info: profiling data set to duplicate + * + * Return newly allocated duplicate on success, %NULL on error. + */ +struct gcov_info * +gcov_info_dup(struct gcov_info *info) +{ + struct gcov_info *dup; + struct gcov_ctr_info *dci_ptr; /* dst counter info */ + struct gcov_ctr_info *sci_ptr; /* src counter info */ + unsigned int active; + unsigned int fi_idx; /* function info idx */ + unsigned int ct_idx; /* counter type idx */ + size_t fi_size; /* function info size */ + size_t cv_size; /* counter values size */ + + if ((dup = malloc(sizeof(*dup), M_GCOV, M_NOWAIT|M_ZERO)) == NULL) + return (NULL); + memcpy(dup, info, sizeof(*dup)); + + dup->next = NULL; + dup->filename = NULL; + dup->functions = NULL; + + dup->filename = strdup_flags(info->filename, M_GCOV, M_NOWAIT); + if (dup->filename == NULL) + goto err_free; + + dup->functions = malloc(info->n_functions * sizeof(struct gcov_fn_info *), M_GCOV, M_NOWAIT|M_ZERO); + if (dup->functions == NULL) + goto err_free; + active = num_counter_active(info); + fi_size = sizeof(struct gcov_fn_info); + fi_size += sizeof(struct gcov_ctr_info) * active; + + for (fi_idx = 0; fi_idx < info->n_functions; fi_idx++) { + dup->functions[fi_idx] = malloc(fi_size, M_GCOV, M_NOWAIT|M_ZERO); + if (!dup->functions[fi_idx]) + goto err_free; + + *(dup->functions[fi_idx]) = *(info->functions[fi_idx]); + + sci_ptr = info->functions[fi_idx]->ctrs; + dci_ptr = dup->functions[fi_idx]->ctrs; + + for (ct_idx = 0; ct_idx < active; ct_idx++) { + + cv_size = sizeof(gcov_type) * sci_ptr->num; + + dci_ptr->values = malloc(cv_size, M_GCOV, M_NOWAIT); + + if (!dci_ptr->values) + goto err_free; + + dci_ptr->num = sci_ptr->num; + memcpy(dci_ptr->values, sci_ptr->values, cv_size); + + sci_ptr++; + dci_ptr++; + } + } + + return (dup); +err_free: + gcov_info_free(dup); + return (NULL); +} + +/** + * gcov_info_free - release memory for profiling data set duplicate + * @info: profiling data set duplicate to free + */ +void +gcov_info_free(struct gcov_info *info) +{ + unsigned int active; + unsigned int fi_idx; + unsigned int ct_idx; + struct gcov_ctr_info *ci_ptr; + + if (!info->functions) + goto free_info; + + active = num_counter_active(info); + + for (fi_idx = 0; fi_idx < info->n_functions; fi_idx++) { + if (!info->functions[fi_idx]) + continue; + + ci_ptr = info->functions[fi_idx]->ctrs; + + for (ct_idx = 0; ct_idx < active; ct_idx++, ci_ptr++) + free(ci_ptr->values, M_GCOV); + + free(info->functions[fi_idx], M_GCOV); + } + +free_info: + free(info->functions, M_GCOV); + free(__DECONST(char *, info->filename), M_GCOV); + free(info, M_GCOV); + } + +#define ITER_STRIDE PAGE_SIZE + +/** + * struct gcov_iterator - specifies current file position in logical records + * @info: associated profiling data + * @buffer: buffer containing file data + * @size: size of buffer + * @pos: current position in file + */ +struct gcov_iterator { + struct gcov_info *info; + caddr_t buffer; + size_t size; + off_t pos; +}; + +/** + * store_gcov_uint32 - store 32 bit number in gcov format to buffer + * @buffer: target buffer or NULL + * @off: offset into the buffer + * @v: value to be stored + * + * Number format defined by gcc: numbers are recorded in the 32 bit + * unsigned binary form of the endianness of the machine generating the + * file. Returns the number of bytes stored. If @buffer is %NULL, doesn't + * store anything. + */ +static size_t +store_gcov_uint32(void *buffer, size_t off, uint32_t v) +{ + uint32_t *data; + + if (buffer) { + data = (void*)((caddr_t)buffer + off); + *data = v; + } + + return sizeof(*data); +} + +/** + * store_gcov_uint64 - store 64 bit number in gcov format to buffer + * @buffer: target buffer or NULL + * @off: offset into the buffer + * @v: value to be stored + * + * Number format defined by gcc: numbers are recorded in the 32 bit + * unsigned binary form of the endianness of the machine generating the + * file. 64 bit numbers are stored as two 32 bit numbers, the low part + * first. Returns the number of bytes stored. If @buffer is %NULL, doesn't store + * anything. + */ + +static size_t +store_gcov_uint64(void *buffer, size_t off, uint64_t v) +{ + uint32_t *data; + + if (buffer) { + data = (void*)((caddr_t)buffer + off); + + data[0] = (v & 0xffffffffUL); + data[1] = (v >> 32); + } + + return sizeof(*data) * 2; +} + +/** + * convert_to_gcda - convert profiling data set to gcda file format + * @buffer: the buffer to store file data or %NULL if no data should be stored + * @info: profiling data set to be converted + * + * Returns the number of bytes that were/would have been stored into the buffer. + */ +static size_t +convert_to_gcda(char *buffer, struct gcov_info *info) +{ + struct gcov_fn_info *fi_ptr; + struct gcov_ctr_info *ci_ptr; + unsigned int fi_idx; + unsigned int ct_idx; + unsigned int cv_idx; + size_t pos = 0; + + /* File header. */ + pos += store_gcov_uint32(buffer, pos, GCOV_DATA_MAGIC); + pos += store_gcov_uint32(buffer, pos, info->version); + pos += store_gcov_uint32(buffer, pos, info->stamp); + + for (fi_idx = 0; fi_idx < info->n_functions; fi_idx++) { + fi_ptr = info->functions[fi_idx]; + + /* Function record. */ + pos += store_gcov_uint32(buffer, pos, GCOV_TAG_FUNCTION); + pos += store_gcov_uint32(buffer, pos, GCOV_TAG_FUNCTION_LENGTH); + pos += store_gcov_uint32(buffer, pos, fi_ptr->ident); + pos += store_gcov_uint32(buffer, pos, fi_ptr->lineno_checksum); + pos += store_gcov_uint32(buffer, pos, fi_ptr->cfg_checksum); + + ci_ptr = fi_ptr->ctrs; + + for (ct_idx = 0; ct_idx < GCOV_COUNTERS; ct_idx++) { + if (!counter_active(info, ct_idx)) + continue; + + /* Counter record. */ + pos += store_gcov_uint32(buffer, pos, + GCOV_TAG_FOR_COUNTER(ct_idx)); + pos += store_gcov_uint32(buffer, pos, ci_ptr->num * 2); + + for (cv_idx = 0; cv_idx < ci_ptr->num; cv_idx++) { + pos += store_gcov_uint64(buffer, pos, + ci_ptr->values[cv_idx]); + } + + ci_ptr++; + } + } + + return (pos); +} + +/** + * gcov_iter_new - allocate and initialize profiling data iterator + * @info: profiling data set to be iterated + * + * Return file iterator on success, %NULL otherwise. + */ +struct gcov_iterator * +gcov_iter_new(struct gcov_info *info) +{ + struct gcov_iterator *iter; + + iter = malloc(sizeof(struct gcov_iterator), M_GCOV, M_NOWAIT|M_ZERO); + if (iter == NULL) + goto err_free; + + iter->info = info; + /* Dry-run to get the actual buffer size. */ + iter->size = convert_to_gcda(NULL, info); + iter->buffer = malloc(iter->size, M_GCOV, M_NOWAIT); + if (!iter->buffer) + goto err_free; + + convert_to_gcda(iter->buffer, info); + + return iter; + +err_free: + free(iter, M_GCOV); + return (NULL); +} + + +/** + * gcov_iter_get_info - return profiling data set for given file iterator + * @iter: file iterator + */ +void +gcov_iter_free(struct gcov_iterator *iter) +{ + free(iter->buffer, M_GCOV); + free(iter, M_GCOV); +} + +/** + * gcov_iter_get_info - return profiling data set for given file iterator + * @iter: file iterator + */ +struct gcov_info * +gcov_iter_get_info(struct gcov_iterator *iter) +{ + return (iter->info); +} + +/** + * gcov_iter_start - reset file iterator to starting position + * @iter: file iterator + */ +void +gcov_iter_start(struct gcov_iterator *iter) +{ + iter->pos = 0; +} + +/** + * gcov_iter_next - advance file iterator to next logical record + * @iter: file iterator + * + * Return zero if new position is valid, non-zero if iterator has reached end. + */ +int +gcov_iter_next(struct gcov_iterator *iter) +{ + if (iter->pos < iter->size) + iter->pos += ITER_STRIDE; + + if (iter->pos >= iter->size) + return (EINVAL); + + return 0; +} + +/** + * gcov_iter_write - write data for current pos to seq_file + * @iter: file iterator + * @seq: seq_file handle + * + * Return zero on success, non-zero otherwise. + */ +int +gcov_iter_write(struct gcov_iterator *iter, struct sbuf *sbuf) +{ + size_t len; + + if (iter->pos >= iter->size) + return (EINVAL); + + len = ITER_STRIDE; + if (iter->pos + len > iter->size) + len = iter->size - iter->pos; + + sbuf_bcat(sbuf, iter->buffer + iter->pos, len); + + return (0); +} Index: head/sys/gnu/gcov/gcov.h =================================================================== --- head/sys/gnu/gcov/gcov.h +++ head/sys/gnu/gcov/gcov.h @@ -0,0 +1,99 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019, Matthew Macy + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#ifndef _SYS_GCOV_H_ +#define _SYS_GCOV_H_ + +MALLOC_DECLARE(M_GCOV); + +/* + * Profiling data types used for gcc 3.4 and above - these are defined by + * gcc and need to be kept as close to the original definition as possible to + * remain compatible. + */ +#define GCOV_DATA_MAGIC ((unsigned int) 0x67636461) +#define GCOV_TAG_FUNCTION ((unsigned int) 0x01000000) +#define GCOV_TAG_COUNTER_BASE ((unsigned int) 0x01a10000) +#define GCOV_TAG_FOR_COUNTER(count) \ + (GCOV_TAG_COUNTER_BASE + ((unsigned int) (count) << 17)) + +typedef uint64_t gcov_type; + +/* Opaque gcov_info. The gcov structures can change as for example in gcc 4.7 so + * we cannot use full definition here and they need to be placed in gcc specific + * implementation of gcov. This also means no direct access to the members in + * generic code and usage of the interface below.*/ +struct gcov_info; + +/* Interface to access gcov_info data */ +const char *gcov_info_filename(struct gcov_info *info); +unsigned int gcov_info_version(struct gcov_info *info); +struct gcov_info *gcov_info_next(struct gcov_info *info); +void gcov_info_link(struct gcov_info *info); +void gcov_info_unlink(struct gcov_info *prev, struct gcov_info *info); + +/* Base interface. */ +enum gcov_action { + GCOV_ADD, + GCOV_REMOVE, +}; + +/* Iterator control. */ +struct gcov_iterator; + +struct gcov_iterator *gcov_iter_new(struct gcov_info *info); +void gcov_iter_free(struct gcov_iterator *iter); +void gcov_iter_start(struct gcov_iterator *iter); +int gcov_iter_next(struct gcov_iterator *iter); +int gcov_iter_write(struct gcov_iterator *iter, struct sbuf *sbuf); +struct gcov_info *gcov_iter_get_info(struct gcov_iterator *iter); + +/* gcov_info control. */ +void gcov_info_reset(struct gcov_info *info); +int gcov_info_is_compatible(struct gcov_info *info1, struct gcov_info *info2); +void gcov_info_add(struct gcov_info *dest, struct gcov_info *source); +struct gcov_info *gcov_info_dup(struct gcov_info *info); +void gcov_info_free(struct gcov_info *info); +void gcov_stats_reset(void); +void gcov_enable_events(void); +void gcov_module_unload(void *, module_t); +void gcov_fs_init(void); + +int within_module(vm_offset_t addr, module_t mod); + +struct gcov_link { + enum { + OBJ_TREE, + SRC_TREE, + } dir; + const char *ext; +}; +extern const struct gcov_link gcov_link[]; +#endif Index: head/sys/gnu/gcov/gcov_fs.c =================================================================== --- head/sys/gnu/gcov/gcov_fs.c +++ head/sys/gnu/gcov/gcov_fs.c @@ -0,0 +1,945 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This code exports profiling data as debugfs files to userspace. + * + * Copyright IBM Corp. 2009 + * Author(s): Peter Oberparleiter + * + * Uses gcc-internal data definitions. + * Based on the gcov-kernel patch by: + * Hubertus Franke + * Nigel Hinds + * Rajan Ravindran + * Peter Oberparleiter + * Paul Larson + * Yi CDL Yang + */ + + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +extern int gcov_events_enabled; +static int gcov_persist; +static struct mtx gcov_mtx; +MTX_SYSINIT(gcov_init, &gcov_mtx, "gcov_mtx", MTX_DEF); +MALLOC_DEFINE(M_GCOV, "gcov", "gcov"); + +void __gcov_init(struct gcov_info *info); +void __gcov_flush(void); +void __gcov_merge_add(gcov_type *counters, unsigned int n_counters); +void __gcov_merge_single(gcov_type *counters, unsigned int n_counters); +void __gcov_merge_delta(gcov_type *counters, unsigned int n_counters); +void __gcov_merge_ior(gcov_type *counters, unsigned int n_counters); +void __gcov_merge_time_profile(gcov_type *counters, unsigned int n_counters); +void __gcov_merge_icall_topn(gcov_type *counters, unsigned int n_counters); +void __gcov_exit(void); + +static void gcov_event(enum gcov_action action, struct gcov_info *info); + + +/* + * Private copy taken from libc + */ +static char * +(basename)(char *path) +{ + char *ptr; + + /* + * If path is a null pointer or points to an empty string, + * basename() shall return a pointer to the string ".". + */ + if (path == NULL || *path == '\0') + return (__DECONST(char *, ".")); + + /* Find end of last pathname component and null terminate it. */ + ptr = path + strlen(path); + while (ptr > path + 1 && *(ptr - 1) == '/') + --ptr; + *ptr-- = '\0'; + + /* Find beginning of last pathname component. */ + while (ptr > path && *(ptr - 1) != '/') + --ptr; + return (ptr); +} + +/* + * __gcov_init is called by gcc-generated constructor code for each object + * file compiled with -fprofile-arcs. + */ +void +__gcov_init(struct gcov_info *info) +{ + static unsigned int gcov_version; + + mtx_lock(&gcov_mtx); + if (gcov_version == 0) { + gcov_version = gcov_info_version(info); + /* + * Printing gcc's version magic may prove useful for debugging + * incompatibility reports. + */ + log(LOG_INFO, "version magic: 0x%x\n", gcov_version); + } + /* + * Add new profiling data structure to list and inform event + * listener. + */ + gcov_info_link(info); + if (gcov_events_enabled) + gcov_event(GCOV_ADD, info); + mtx_unlock(&gcov_mtx); +} + +/* + * These functions may be referenced by gcc-generated profiling code but serve + * no function for kernel profiling. + */ +void +__gcov_flush(void) +{ + /* Unused. */ +} + +void +__gcov_merge_add(gcov_type *counters, unsigned int n_counters) +{ + /* Unused. */ +} + +void +__gcov_merge_single(gcov_type *counters, unsigned int n_counters) +{ + /* Unused. */ +} + +void +__gcov_merge_delta(gcov_type *counters, unsigned int n_counters) +{ + /* Unused. */ +} + +void +__gcov_merge_ior(gcov_type *counters, unsigned int n_counters) +{ + /* Unused. */ +} + +void +__gcov_merge_time_profile(gcov_type *counters, unsigned int n_counters) +{ + /* Unused. */ +} + +void +__gcov_merge_icall_topn(gcov_type *counters, unsigned int n_counters) +{ + /* Unused. */ +} + +void +__gcov_exit(void) +{ + /* Unused. */ +} + + +/** + * struct gcov_node - represents a debugfs entry + * @entry: list entry for parent's child node list + * @children: child nodes + * @all_entry: list entry for list of all nodes + * @parent: parent node + * @loaded_info: array of pointers to profiling data sets for loaded object + * files. + * @num_loaded: number of profiling data sets for loaded object files. + * @unloaded_info: accumulated copy of profiling data sets for unloaded + * object files. Used only when gcov_persist=1. + * @dentry: main debugfs entry, either a directory or data file + * @links: associated symbolic links + * @name: data file basename + * + * struct gcov_node represents an entity within the gcov/ subdirectory + * of debugfs. There are directory and data file nodes. The latter represent + * the actual synthesized data file plus any associated symbolic links which + * are needed by the gcov tool to work correctly. + */ +struct gcov_node { + LIST_ENTRY(gcov_node) children_entry; + LIST_ENTRY(gcov_node) all_entry; + struct { + struct gcov_node *lh_first; + } children; + struct gcov_node *parent; + struct gcov_info **loaded_info; + struct gcov_info *unloaded_info; + struct dentry *dentry; + struct dentry **links; + int num_loaded; + char name[0]; +}; + +#ifdef notyet +static const char objtree[] = OBJTREE; +static const char srctree[] = SRCTREE; +#else +static const char objtree[] = ""; +static const char srctree[] = ""; +#endif +static struct gcov_node root_node; +static struct { + struct gcov_node *lh_first; +} all_head; +static struct mtx node_lock; +MTX_SYSINIT(node_init, &node_lock, "node_lock", MTX_DEF); +static void remove_node(struct gcov_node *node); + +/* + * seq_file.start() implementation for gcov data files. Note that the + * gcov_iterator interface is designed to be more restrictive than seq_file + * (no start from arbitrary position, etc.), to simplify the iterator + * implementation. + */ +static void * +gcov_seq_start(struct seq_file *seq, off_t *pos) +{ + off_t i; + + gcov_iter_start(seq->private); + for (i = 0; i < *pos; i++) { + if (gcov_iter_next(seq->private)) + return NULL; + } + return seq->private; +} + +/* seq_file.next() implementation for gcov data files. */ +static void * +gcov_seq_next(struct seq_file *seq, void *data, off_t *pos) +{ + struct gcov_iterator *iter = data; + + if (gcov_iter_next(iter)) + return NULL; + (*pos)++; + + return iter; +} + +/* seq_file.show() implementation for gcov data files. */ +static int +gcov_seq_show(struct seq_file *seq, void *data) +{ + struct gcov_iterator *iter = data; + + if (gcov_iter_write(iter, seq->buf)) + return (-EINVAL); + return (0); +} + +static void +gcov_seq_stop(struct seq_file *seq, void *data) +{ + /* Unused. */ +} + +static const struct seq_operations gcov_seq_ops = { + .start = gcov_seq_start, + .next = gcov_seq_next, + .show = gcov_seq_show, + .stop = gcov_seq_stop, +}; + +/* + * Return a profiling data set associated with the given node. This is + * either a data set for a loaded object file or a data set copy in case + * all associated object files have been unloaded. + */ +static struct gcov_info * +get_node_info(struct gcov_node *node) +{ + if (node->num_loaded > 0) + return (node->loaded_info[0]); + + return (node->unloaded_info); +} + +/* + * Return a newly allocated profiling data set which contains the sum of + * all profiling data associated with the given node. + */ +static struct gcov_info * +get_accumulated_info(struct gcov_node *node) +{ + struct gcov_info *info; + int i = 0; + + if (node->unloaded_info) + info = gcov_info_dup(node->unloaded_info); + else + info = gcov_info_dup(node->loaded_info[i++]); + if (info == NULL) + return (NULL); + for (; i < node->num_loaded; i++) + gcov_info_add(info, node->loaded_info[i]); + + return (info); +} + +/* + * open() implementation for gcov data files. Create a copy of the profiling + * data set and initialize the iterator and seq_file interface. + */ +static int +gcov_seq_open(struct inode *inode, struct file *file) +{ + struct gcov_node *node = inode->i_private; + struct gcov_iterator *iter; + struct seq_file *seq; + struct gcov_info *info; + int rc = -ENOMEM; + + mtx_lock(&node_lock); + /* + * Read from a profiling data copy to minimize reference tracking + * complexity and concurrent access and to keep accumulating multiple + * profiling data sets associated with one node simple. + */ + info = get_accumulated_info(node); + if (info == NULL) + goto out_unlock; + iter = gcov_iter_new(info); + if (iter == NULL) + goto err_free_info; + rc = seq_open(file, &gcov_seq_ops); + if (rc) + goto err_free_iter_info; + seq = file->private_data; + seq->private = iter; +out_unlock: + mtx_unlock(&node_lock); + return (rc); + +err_free_iter_info: + gcov_iter_free(iter); +err_free_info: + gcov_info_free(info); + goto out_unlock; +} + +/* + * release() implementation for gcov data files. Release resources allocated + * by open(). + */ +static int +gcov_seq_release(struct inode *inode, struct file *file) +{ + struct gcov_iterator *iter; + struct gcov_info *info; + struct seq_file *seq; + + seq = file->private_data; + iter = seq->private; + info = gcov_iter_get_info(iter); + gcov_iter_free(iter); + gcov_info_free(info); + seq_release(inode, file); + + return (0); +} + +/* + * Find a node by the associated data file name. Needs to be called with + * node_lock held. + */ +static struct gcov_node * +get_node_by_name(const char *name) +{ + struct gcov_node *node; + struct gcov_info *info; + + LIST_FOREACH(node, &all_head, all_entry) { + info = get_node_info(node); + if (info && (strcmp(gcov_info_filename(info), name) == 0)) + return (node); + } + + return (NULL); +} + +/* + * Reset all profiling data associated with the specified node. + */ +static void +reset_node(struct gcov_node *node) +{ + int i; + + if (node->unloaded_info) + gcov_info_reset(node->unloaded_info); + for (i = 0; i < node->num_loaded; i++) + gcov_info_reset(node->loaded_info[i]); +} + +void +gcov_stats_reset(void) +{ + struct gcov_node *node; + + mtx_lock(&node_lock); + restart: + LIST_FOREACH(node, &all_head, all_entry) { + if (node->num_loaded > 0) + reset_node(node); + else if (LIST_EMPTY(&node->children)) { + remove_node(node); + goto restart; + } + } + mtx_unlock(&node_lock); +} + +/* + * write() implementation for gcov data files. Reset profiling data for the + * corresponding file. If all associated object files have been unloaded, + * remove the debug fs node as well. + */ +static ssize_t +gcov_seq_write(struct file *file, const char *addr, size_t len, off_t *pos) +{ + struct seq_file *seq; + struct gcov_info *info; + struct gcov_node *node; + + seq = file->private_data; + info = gcov_iter_get_info(seq->private); + mtx_lock(&node_lock); + node = get_node_by_name(gcov_info_filename(info)); + if (node) { + /* Reset counts or remove node for unloaded modules. */ + if (node->num_loaded == 0) + remove_node(node); + else + reset_node(node); + } + /* Reset counts for open file. */ + gcov_info_reset(info); + mtx_unlock(&node_lock); + + return (len); +} + +/* + * Given a string representing a file path of format: + * path/to/file.gcda + * construct and return a new string: + * path/to/file. + */ +static char * +link_target(const char *dir, const char *path, const char *ext) +{ + char *target; + char *old_ext; + char *copy; + + copy = strdup_flags(path, M_GCOV, M_NOWAIT); + if (!copy) + return (NULL); + old_ext = strrchr(copy, '.'); + if (old_ext) + *old_ext = '\0'; + target = NULL; + if (dir) + asprintf(&target, M_GCOV, "%s/%s.%s", dir, copy, ext); + else + asprintf(&target, M_GCOV, "%s.%s", copy, ext); + free(copy, M_GCOV); + + return (target); +} + +/* + * Construct a string representing the symbolic link target for the given + * gcov data file name and link type. Depending on the link type and the + * location of the data file, the link target can either point to a + * subdirectory of srctree, objtree or in an external location. + */ +static char * +get_link_target(const char *filename, const struct gcov_link *ext) +{ + const char *rel; + char *result; + + if (strncmp(filename, objtree, strlen(objtree)) == 0) { + rel = filename + strlen(objtree) + 1; + if (ext->dir == SRC_TREE) + result = link_target(srctree, rel, ext->ext); + else + result = link_target(objtree, rel, ext->ext); + } else { + /* External compilation. */ + result = link_target(NULL, filename, ext->ext); + } + + return (result); +} + +#define SKEW_PREFIX ".tmp_" + +/* + * For a filename .tmp_filename.ext return filename.ext. Needed to compensate + * for filename skewing caused by the mod-versioning mechanism. + */ +static const char * +deskew(const char *basename) +{ + if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0) + return (basename + sizeof(SKEW_PREFIX) - 1); + return (basename); +} + +/* + * Create links to additional files (usually .c and .gcno files) which the + * gcov tool expects to find in the same directory as the gcov data file. + */ +static void +add_links(struct gcov_node *node, struct dentry *parent) +{ + const char *path_basename; + char *target; + int num; + int i; + + for (num = 0; gcov_link[num].ext; num++) + /* Nothing. */; + node->links = malloc((num*sizeof(struct dentry *)), M_GCOV, M_NOWAIT|M_ZERO); + if (node->links == NULL) + return; + for (i = 0; i < num; i++) { + target = get_link_target( + gcov_info_filename(get_node_info(node)), + &gcov_link[i]); + if (target == NULL) + goto out_err; + path_basename = basename(target); + if (path_basename == target) + goto out_err; + node->links[i] = debugfs_create_symlink(deskew(path_basename), + parent, target); + if (!node->links[i]) + goto out_err; + free(target, M_GCOV); + } + + return; +out_err: + free(target, M_GCOV); + while (i-- > 0) + debugfs_remove(node->links[i]); + free(node->links, M_GCOV); + node->links = NULL; +} + +static const struct file_operations gcov_data_fops = { + .open = gcov_seq_open, + .release = gcov_seq_release, + .read = seq_read, + .llseek = seq_lseek, + .write = gcov_seq_write, +}; + +/* Basic initialization of a new node. */ +static void +init_node(struct gcov_node *node, struct gcov_info *info, + const char *name, struct gcov_node *parent) +{ + LIST_INIT(&node->children); + if (node->loaded_info) { + node->loaded_info[0] = info; + node->num_loaded = 1; + } + node->parent = parent; + if (name) + strcpy(node->name, name); +} + +/* + * Create a new node and associated debugfs entry. Needs to be called with + * node_lock held. + */ +static struct gcov_node * +new_node(struct gcov_node *parent, struct gcov_info *info, const char *name) +{ + struct gcov_node *node; + + node = malloc(sizeof(struct gcov_node) + strlen(name) + 1, M_GCOV, M_NOWAIT|M_ZERO); + if (!node) + goto err_nomem; + if (info) { + node->loaded_info = malloc(sizeof(struct gcov_info *), M_GCOV, M_NOWAIT|M_ZERO); + if (!node->loaded_info) + goto err_nomem; + } + init_node(node, info, name, parent); + /* Differentiate between gcov data file nodes and directory nodes. */ + if (info) { + node->dentry = debugfs_create_file(deskew(node->name), 0600, + parent->dentry, node, &gcov_data_fops); + } else + node->dentry = debugfs_create_dir(node->name, parent->dentry); + if (!node->dentry) { + log(LOG_WARNING, "could not create file\n"); + free(node, M_GCOV); + return NULL; + } + if (info) + add_links(node, parent->dentry); + LIST_INSERT_HEAD(&parent->children, node, children_entry); + LIST_INSERT_HEAD(&all_head, node, all_entry); + + return (node); + +err_nomem: + free(node, M_GCOV); + log(LOG_WARNING, "out of memory\n"); + return NULL; +} + +/* Remove symbolic links associated with node. */ +static void +remove_links(struct gcov_node *node) +{ + + if (node->links == NULL) + return; + for (int i = 0; gcov_link[i].ext; i++) + debugfs_remove(node->links[i]); + free(node->links, M_GCOV); + node->links = NULL; +} + +/* + * Remove node from all lists and debugfs and release associated resources. + * Needs to be called with node_lock held. + */ +static void +release_node(struct gcov_node *node) +{ + LIST_REMOVE(node, children_entry); + LIST_REMOVE(node, all_entry); + debugfs_remove(node->dentry); + remove_links(node); + free(node->loaded_info, M_GCOV); + if (node->unloaded_info) + gcov_info_free(node->unloaded_info); + free(node, M_GCOV); +} + +/* Release node and empty parents. Needs to be called with node_lock held. */ +static void +remove_node(struct gcov_node *node) +{ + struct gcov_node *parent; + + while ((node != &root_node) && LIST_EMPTY(&node->children)) { + parent = node->parent; + release_node(node); + node = parent; + } +} + +/* + * Find child node with given basename. Needs to be called with node_lock + * held. + */ +static struct gcov_node * +get_child_by_name(struct gcov_node *parent, const char *name) +{ + struct gcov_node *node; + + LIST_FOREACH(node, &parent->children, children_entry) { + if (strcmp(node->name, name) == 0) + return (node); + } + + return (NULL); +} + +/* + * Create a node for a given profiling data set and add it to all lists and + * debugfs. Needs to be called with node_lock held. + */ +static void +add_node(struct gcov_info *info) +{ + char *filename; + char *curr; + char *next; + struct gcov_node *parent; + struct gcov_node *node; + + filename = strdup_flags(gcov_info_filename(info), M_GCOV, M_NOWAIT); + if (filename == NULL) + return; + parent = &root_node; + /* Create directory nodes along the path. */ + for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) { + if (curr == next) + continue; + *next = 0; + if (strcmp(curr, ".") == 0) + continue; + if (strcmp(curr, "..") == 0) { + if (!parent->parent) + goto err_remove; + parent = parent->parent; + continue; + } + node = get_child_by_name(parent, curr); + if (!node) { + node = new_node(parent, NULL, curr); + if (!node) + goto err_remove; + } + parent = node; + } + /* Create file node. */ + node = new_node(parent, info, curr); + if (!node) + goto err_remove; +out: + free(filename, M_GCOV); + return; + +err_remove: + remove_node(parent); + goto out; +} + +/* + * Associate a profiling data set with an existing node. Needs to be called + * with node_lock held. + */ +static void +add_info(struct gcov_node *node, struct gcov_info *info) +{ + struct gcov_info **loaded_info; + int num = node->num_loaded; + + /* + * Prepare new array. This is done first to simplify cleanup in + * case the new data set is incompatible, the node only contains + * unloaded data sets and there's not enough memory for the array. + */ + loaded_info = malloc((num + 1)* sizeof(struct gcov_info *), M_GCOV, M_NOWAIT|M_ZERO); + if (!loaded_info) { + log(LOG_WARNING, "could not add '%s' (out of memory)\n", + gcov_info_filename(info)); + return; + } + memcpy(loaded_info, node->loaded_info, + num * sizeof(struct gcov_info *)); + loaded_info[num] = info; + /* Check if the new data set is compatible. */ + if (num == 0) { + /* + * A module was unloaded, modified and reloaded. The new + * data set replaces the copy of the last one. + */ + if (!gcov_info_is_compatible(node->unloaded_info, info)) { + log(LOG_WARNING, "discarding saved data for %s " + "(incompatible version)\n", + gcov_info_filename(info)); + gcov_info_free(node->unloaded_info); + node->unloaded_info = NULL; + } + } else { + /* + * Two different versions of the same object file are loaded. + * The initial one takes precedence. + */ + if (!gcov_info_is_compatible(node->loaded_info[0], info)) { + log(LOG_WARNING, "could not add '%s' (incompatible " + "version)\n", gcov_info_filename(info)); + free(loaded_info, M_GCOV); + return; + } + } + /* Overwrite previous array. */ + free(node->loaded_info, M_GCOV); + node->loaded_info = loaded_info; + node->num_loaded = num + 1; +} + +/* + * Return the index of a profiling data set associated with a node. + */ +static int +get_info_index(struct gcov_node *node, struct gcov_info *info) +{ + int i; + + for (i = 0; i < node->num_loaded; i++) { + if (node->loaded_info[i] == info) + return (i); + } + return (ENOENT); +} + +/* + * Save the data of a profiling data set which is being unloaded. + */ +static void +save_info(struct gcov_node *node, struct gcov_info *info) +{ + if (node->unloaded_info) + gcov_info_add(node->unloaded_info, info); + else { + node->unloaded_info = gcov_info_dup(info); + if (!node->unloaded_info) { + log(LOG_WARNING, "could not save data for '%s' " + "(out of memory)\n", + gcov_info_filename(info)); + } + } +} + +/* + * Disassociate a profiling data set from a node. Needs to be called with + * node_lock held. + */ +static void +remove_info(struct gcov_node *node, struct gcov_info *info) +{ + int i; + + i = get_info_index(node, info); + if (i < 0) { + log(LOG_WARNING, "could not remove '%s' (not found)\n", + gcov_info_filename(info)); + return; + } + if (gcov_persist) + save_info(node, info); + /* Shrink array. */ + node->loaded_info[i] = node->loaded_info[node->num_loaded - 1]; + node->num_loaded--; + if (node->num_loaded > 0) + return; + /* Last loaded data set was removed. */ + free(node->loaded_info, M_GCOV); + node->loaded_info = NULL; + node->num_loaded = 0; + if (!node->unloaded_info) + remove_node(node); +} + +/* + * Callback to create/remove profiling files when code compiled with + * -fprofile-arcs is loaded/unloaded. + */ +static void +gcov_event(enum gcov_action action, struct gcov_info *info) +{ + struct gcov_node *node; + + mtx_lock(&node_lock); + node = get_node_by_name(gcov_info_filename(info)); + switch (action) { + case GCOV_ADD: + if (node) + add_info(node, info); + else + add_node(info); + break; + case GCOV_REMOVE: + if (node) + remove_info(node, info); + else { + log(LOG_WARNING, "could not remove '%s' (not found)\n", + gcov_info_filename(info)); + } + break; + } + mtx_unlock(&node_lock); +} + +/** + * gcov_enable_events - enable event reporting through gcov_event() + * + * Turn on reporting of profiling data load/unload-events through the + * gcov_event() callback. Also replay all previous events once. This function + * is needed because some events are potentially generated too early for the + * callback implementation to handle them initially. + */ +void +gcov_enable_events(void) +{ + struct gcov_info *info = NULL; + int count; + + mtx_lock(&gcov_mtx); + count = 0; + + /* Perform event callback for previously registered entries. */ + while ((info = gcov_info_next(info))) { + gcov_event(GCOV_ADD, info); + sched_relinquish(curthread); + count++; + } + + mtx_unlock(&gcov_mtx); + printf("%s found %d events\n", __func__, count); +} + +/* Update list and generate events when modules are unloaded. */ +void +gcov_module_unload(void *arg __unused, module_t mod) +{ + struct gcov_info *info = NULL; + struct gcov_info *prev = NULL; + + mtx_lock(&gcov_mtx ); + + /* Remove entries located in module from linked list. */ + while ((info = gcov_info_next(info))) { + if (within_module((vm_offset_t)info, mod)) { + gcov_info_unlink(prev, info); + if (gcov_events_enabled) + gcov_event(GCOV_REMOVE, info); + } else + prev = info; + } + + mtx_unlock(&gcov_mtx); +} + +void +gcov_fs_init(void) +{ + init_node(&root_node, NULL, NULL, NULL); + root_node.dentry = debugfs_create_dir("gcov", NULL); +} Index: head/sys/gnu/gcov/gcov_subr.c =================================================================== --- head/sys/gnu/gcov/gcov_subr.c +++ head/sys/gnu/gcov/gcov_subr.c @@ -0,0 +1,169 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019, Matthew Macy + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "linker_if.h" + + +static void gcov_invoke_ctors(void); +static int gcov_ctors_done; +int gcov_events_enabled; + +static int +gcov_stats_reset_sysctl(SYSCTL_HANDLER_ARGS) +{ + int error, v; + + v = 0; + error = sysctl_handle_int(oidp, &v, 0, req); + if (error) + return (error); + if (req->newptr == NULL) + return (error); + if (v == 0) + return (0); + gcov_stats_reset(); + + return (0); +} + +static int +gcov_stats_enable_sysctl(SYSCTL_HANDLER_ARGS) +{ + int error, v; + + v = gcov_events_enabled; + error = sysctl_handle_int(oidp, &v, v, req); + if (error) + return (error); + if (req->newptr == NULL) + return (error); + if (v == gcov_events_enabled) + return (0); + //gcov_events_reset(); + gcov_events_enabled = !!v; + if (!gcov_ctors_done) + gcov_invoke_ctors(); + if (gcov_events_enabled) + gcov_enable_events(); + + return (0); +} + +int +within_module(vm_offset_t addr, module_t mod) +{ + linker_file_t link_info; + vm_offset_t mod_addr; + size_t mod_size; + + link_info = module_file(mod); + mod_addr = (vm_offset_t)link_info->address; + mod_size = link_info->size; + if (addr >= mod_addr && addr < mod_addr + mod_size) + return (1); + return (0); +} + + + +#define GCOV_PREFIX "_GLOBAL__sub_I_65535_0_" + +static int +gcov_invoke_ctor(const char *name, void *arg) +{ + void (*ctor)(void); + c_linker_sym_t sym; + linker_symval_t symval; + linker_file_t lf; + + if (strstr(name, GCOV_PREFIX) == NULL) + return (0); + lf = arg; + LINKER_LOOKUP_SYMBOL(lf, name, &sym); + LINKER_SYMBOL_VALUES(lf, sym, &symval); + ctor = (void *)symval.value; + ctor(); + return (0); +} + +static int +gcov_invoke_lf_ctors(linker_file_t lf, void *arg __unused) +{ + + printf("%s processing file: %s\n", __func__, lf->filename); + LINKER_EACH_FUNCTION_NAME(lf, gcov_invoke_ctor, lf); + return (0); +} + +static void +gcov_invoke_ctors(void) +{ + + gcov_fs_init(); + + linker_file_foreach(gcov_invoke_lf_ctors, NULL); + gcov_ctors_done = 1; +} + +static int +gcov_init(void *arg __unused) +{ + EVENTHANDLER_REGISTER(module_unload, gcov_module_unload, NULL, 0); + gcov_enable_events(); + return (0); +} + +SYSINIT(gcov_init, SI_SUB_EVENTHANDLER, SI_ORDER_ANY, gcov_init, NULL); + +static SYSCTL_NODE(_debug, OID_AUTO, gcov, CTLFLAG_RD, NULL, + "gcov code coverage"); +SYSCTL_PROC(_debug_gcov, OID_AUTO, reset, CTLTYPE_INT | CTLFLAG_RW, + NULL, 0, gcov_stats_reset_sysctl, "I", "Reset all profiling counts"); +SYSCTL_PROC(_debug_gcov, OID_AUTO, enable, CTLTYPE_INT | CTLFLAG_RW, + NULL, 0, gcov_stats_enable_sysctl, "I", "Enable code coverage");