// SPDX-License-Identifier: GPL-2.01// This program is free software; you can redistribute it and/or2// modify it under the terms of the GNU General Public License3// as published by the Free Software Foundation; version 2.4//5// This program is distributed in the hope that it will be useful,6// but WITHOUT ANY WARRANTY; without even the implied warranty of7// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the8// GNU General Public License for more details.9//10// You should have received a copy of the GNU General Public License11// along with this program; if not, write to the Free Software12// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA13// 02110-1301, USA.14/*15* This code provides functions to handle gcc's profiling data format16* introduced with gcc 4.7.17*18* This file is based heavily on gcc_3_4.c file.19*20* For a better understanding, refer to gcc source:21* gcc/gcov-io.h22* libgcc/libgcov.c23*24* Uses gcc-internal data definitions.25*/2627#include <sys/param.h>28#include <sys/systm.h>29#include <sys/types.h>30#include <sys/systm.h>31#include <sys/sbuf.h>32#include <sys/malloc.h>33#include <sys/module.h>34#include <gnu/gcov/gcov.h>353637#if (__GNUC__ >= 7)38#define GCOV_COUNTERS 939#elif (__GNUC__ > 5) || (__GNUC__ == 5 && __GNUC_MINOR__ >= 1)40#define GCOV_COUNTERS 1041#elif __GNUC__ == 4 && __GNUC_MINOR__ >= 942#define GCOV_COUNTERS 943#else44#define GCOV_COUNTERS 845#endif4647#define GCOV_TAG_FUNCTION_LENGTH 34849static struct gcov_info *gcov_info_head;5051/**52* struct gcov_ctr_info - information about counters for a single function53* @num: number of counter values for this type54* @values: array of counter values for this type55*56* This data is generated by gcc during compilation and doesn't change57* at run-time with the exception of the values array.58*/59struct gcov_ctr_info {60unsigned int num;61gcov_type *values;62};6364/**65* struct gcov_fn_info - profiling meta data per function66* @key: comdat key67* @ident: unique ident of function68* @lineno_checksum: function lineo_checksum69* @cfg_checksum: function cfg checksum70* @ctrs: instrumented counters71*72* This data is generated by gcc during compilation and doesn't change73* at run-time.74*75* Information about a single function. This uses the trailing array76* idiom. The number of counters is determined from the merge pointer77* array in gcov_info. The key is used to detect which of a set of78* comdat functions was selected -- it points to the gcov_info object79* of the object file containing the selected comdat function.80*/81struct gcov_fn_info {82const struct gcov_info *key;83unsigned int ident;84unsigned int lineno_checksum;85unsigned int cfg_checksum;86struct gcov_ctr_info ctrs[0];87};8889/**90* struct gcov_info - profiling data per object file91* @version: gcov version magic indicating the gcc version used for compilation92* @next: list head for a singly-linked list93* @stamp: uniquifying time stamp94* @filename: name of the associated gcov data file95* @merge: merge functions (null for unused counter type)96* @n_functions: number of instrumented functions97* @functions: pointer to pointers to function information98*99* This data is generated by gcc during compilation and doesn't change100* at run-time with the exception of the next pointer.101*/102struct gcov_info {103unsigned int version;104struct gcov_info *next;105unsigned int stamp;106const char *filename;107void (*merge[GCOV_COUNTERS])(gcov_type *, unsigned int);108unsigned int n_functions;109struct gcov_fn_info **functions;110};111112/**113* gcov_info_filename - return info filename114* @info: profiling data set115*/116const char *117gcov_info_filename(struct gcov_info *info)118{119return (info->filename);120}121122/**123* gcov_info_version - return info version124* @info: profiling data set125*/126unsigned int127gcov_info_version(struct gcov_info *info)128{129return (info->version);130}131132/**133* gcov_info_next - return next profiling data set134* @info: profiling data set135*136* Returns next gcov_info following @info or first gcov_info in the chain if137* @info is %NULL.138*/139struct gcov_info *140gcov_info_next(struct gcov_info *info)141{142if (!info)143return gcov_info_head;144145return (info->next);146}147148/**149* gcov_info_link - link/add profiling data set to the list150* @info: profiling data set151*/152void153gcov_info_link(struct gcov_info *info)154{155info->next = gcov_info_head;156gcov_info_head = info;157}158159/**160* gcov_info_unlink - unlink/remove profiling data set from the list161* @prev: previous profiling data set162* @info: profiling data set163*/164void165gcov_info_unlink(struct gcov_info *prev, struct gcov_info *info)166{167if (prev)168prev->next = info->next;169else170gcov_info_head = info->next;171}172173/* Symbolic links to be created for each profiling data file. */174const struct gcov_link gcov_link[] = {175{ OBJ_TREE, "gcno" }, /* Link to .gcno file in $(objtree). */176{ 0, NULL},177};178179/*180* Determine whether a counter is active. Doesn't change at run-time.181*/182static int183counter_active(struct gcov_info *info, unsigned int type)184{185return (info->merge[type] ? 1 : 0);186}187188/* Determine number of active counters. Based on gcc magic. */189static unsigned int190num_counter_active(struct gcov_info *info)191{192unsigned int i;193unsigned int result = 0;194195for (i = 0; i < GCOV_COUNTERS; i++) {196if (counter_active(info, i))197result++;198}199return (result);200}201202/**203* gcov_info_reset - reset profiling data to zero204* @info: profiling data set205*/206void207gcov_info_reset(struct gcov_info *info)208{209struct gcov_ctr_info *ci_ptr;210unsigned int fi_idx;211unsigned int ct_idx;212213for (fi_idx = 0; fi_idx < info->n_functions; fi_idx++) {214ci_ptr = info->functions[fi_idx]->ctrs;215216for (ct_idx = 0; ct_idx < GCOV_COUNTERS; ct_idx++) {217if (!counter_active(info, ct_idx))218continue;219220memset(ci_ptr->values, 0,221sizeof(gcov_type) * ci_ptr->num);222ci_ptr++;223}224}225}226227/**228* gcov_info_is_compatible - check if profiling data can be added229* @info1: first profiling data set230* @info2: second profiling data set231*232* Returns non-zero if profiling data can be added, zero otherwise.233*/234int235gcov_info_is_compatible(struct gcov_info *info1, struct gcov_info *info2)236{237return (info1->stamp == info2->stamp);238}239240/**241* gcov_info_add - add up profiling data242* @dest: profiling data set to which data is added243* @source: profiling data set which is added244*245* Adds profiling counts of @source to @dest.246*/247void248gcov_info_add(struct gcov_info *dst, struct gcov_info *src)249{250struct gcov_ctr_info *dci_ptr;251struct gcov_ctr_info *sci_ptr;252unsigned int fi_idx;253unsigned int ct_idx;254unsigned int val_idx;255256for (fi_idx = 0; fi_idx < src->n_functions; fi_idx++) {257dci_ptr = dst->functions[fi_idx]->ctrs;258sci_ptr = src->functions[fi_idx]->ctrs;259260for (ct_idx = 0; ct_idx < GCOV_COUNTERS; ct_idx++) {261if (!counter_active(src, ct_idx))262continue;263264for (val_idx = 0; val_idx < sci_ptr->num; val_idx++)265dci_ptr->values[val_idx] +=266sci_ptr->values[val_idx];267268dci_ptr++;269sci_ptr++;270}271}272}273274/**275* gcov_info_dup - duplicate profiling data set276* @info: profiling data set to duplicate277*278* Return newly allocated duplicate on success, %NULL on error.279*/280struct gcov_info *281gcov_info_dup(struct gcov_info *info)282{283struct gcov_info *dup;284struct gcov_ctr_info *dci_ptr; /* dst counter info */285struct gcov_ctr_info *sci_ptr; /* src counter info */286unsigned int active;287unsigned int fi_idx; /* function info idx */288unsigned int ct_idx; /* counter type idx */289size_t fi_size; /* function info size */290size_t cv_size; /* counter values size */291292if ((dup = malloc(sizeof(*dup), M_GCOV, M_NOWAIT|M_ZERO)) == NULL)293return (NULL);294memcpy(dup, info, sizeof(*dup));295296dup->next = NULL;297dup->filename = NULL;298dup->functions = NULL;299300dup->filename = strdup_flags(info->filename, M_GCOV, M_NOWAIT);301if (dup->filename == NULL)302goto err_free;303304dup->functions = malloc(info->n_functions * sizeof(struct gcov_fn_info *), M_GCOV, M_NOWAIT|M_ZERO);305if (dup->functions == NULL)306goto err_free;307active = num_counter_active(info);308fi_size = sizeof(struct gcov_fn_info);309fi_size += sizeof(struct gcov_ctr_info) * active;310311for (fi_idx = 0; fi_idx < info->n_functions; fi_idx++) {312dup->functions[fi_idx] = malloc(fi_size, M_GCOV, M_NOWAIT|M_ZERO);313if (!dup->functions[fi_idx])314goto err_free;315316*(dup->functions[fi_idx]) = *(info->functions[fi_idx]);317318sci_ptr = info->functions[fi_idx]->ctrs;319dci_ptr = dup->functions[fi_idx]->ctrs;320321for (ct_idx = 0; ct_idx < active; ct_idx++) {322323cv_size = sizeof(gcov_type) * sci_ptr->num;324325dci_ptr->values = malloc(cv_size, M_GCOV, M_NOWAIT);326327if (!dci_ptr->values)328goto err_free;329330dci_ptr->num = sci_ptr->num;331memcpy(dci_ptr->values, sci_ptr->values, cv_size);332333sci_ptr++;334dci_ptr++;335}336}337338return (dup);339err_free:340gcov_info_free(dup);341return (NULL);342}343344/**345* gcov_info_free - release memory for profiling data set duplicate346* @info: profiling data set duplicate to free347*/348void349gcov_info_free(struct gcov_info *info)350{351unsigned int active;352unsigned int fi_idx;353unsigned int ct_idx;354struct gcov_ctr_info *ci_ptr;355356if (!info->functions)357goto free_info;358359active = num_counter_active(info);360361for (fi_idx = 0; fi_idx < info->n_functions; fi_idx++) {362if (!info->functions[fi_idx])363continue;364365ci_ptr = info->functions[fi_idx]->ctrs;366367for (ct_idx = 0; ct_idx < active; ct_idx++, ci_ptr++)368free(ci_ptr->values, M_GCOV);369370free(info->functions[fi_idx], M_GCOV);371}372373free_info:374free(info->functions, M_GCOV);375free(__DECONST(char *, info->filename), M_GCOV);376free(info, M_GCOV);377}378379#define ITER_STRIDE PAGE_SIZE380381/**382* struct gcov_iterator - specifies current file position in logical records383* @info: associated profiling data384* @buffer: buffer containing file data385* @size: size of buffer386* @pos: current position in file387*/388struct gcov_iterator {389struct gcov_info *info;390caddr_t buffer;391size_t size;392off_t pos;393};394395/**396* store_gcov_uint32 - store 32 bit number in gcov format to buffer397* @buffer: target buffer or NULL398* @off: offset into the buffer399* @v: value to be stored400*401* Number format defined by gcc: numbers are recorded in the 32 bit402* unsigned binary form of the endianness of the machine generating the403* file. Returns the number of bytes stored. If @buffer is %NULL, doesn't404* store anything.405*/406static size_t407store_gcov_uint32(void *buffer, size_t off, uint32_t v)408{409uint32_t *data;410411if (buffer) {412data = (void*)((caddr_t)buffer + off);413*data = v;414}415416return sizeof(*data);417}418419/**420* store_gcov_uint64 - store 64 bit number in gcov format to buffer421* @buffer: target buffer or NULL422* @off: offset into the buffer423* @v: value to be stored424*425* Number format defined by gcc: numbers are recorded in the 32 bit426* unsigned binary form of the endianness of the machine generating the427* file. 64 bit numbers are stored as two 32 bit numbers, the low part428* first. Returns the number of bytes stored. If @buffer is %NULL, doesn't store429* anything.430*/431432static size_t433store_gcov_uint64(void *buffer, size_t off, uint64_t v)434{435uint32_t *data;436437if (buffer) {438data = (void*)((caddr_t)buffer + off);439440data[0] = (v & 0xffffffffUL);441data[1] = (v >> 32);442}443444return sizeof(*data) * 2;445}446447/**448* convert_to_gcda - convert profiling data set to gcda file format449* @buffer: the buffer to store file data or %NULL if no data should be stored450* @info: profiling data set to be converted451*452* Returns the number of bytes that were/would have been stored into the buffer.453*/454static size_t455convert_to_gcda(char *buffer, struct gcov_info *info)456{457struct gcov_fn_info *fi_ptr;458struct gcov_ctr_info *ci_ptr;459unsigned int fi_idx;460unsigned int ct_idx;461unsigned int cv_idx;462size_t pos = 0;463464/* File header. */465pos += store_gcov_uint32(buffer, pos, GCOV_DATA_MAGIC);466pos += store_gcov_uint32(buffer, pos, info->version);467pos += store_gcov_uint32(buffer, pos, info->stamp);468469for (fi_idx = 0; fi_idx < info->n_functions; fi_idx++) {470fi_ptr = info->functions[fi_idx];471472/* Function record. */473pos += store_gcov_uint32(buffer, pos, GCOV_TAG_FUNCTION);474pos += store_gcov_uint32(buffer, pos, GCOV_TAG_FUNCTION_LENGTH);475pos += store_gcov_uint32(buffer, pos, fi_ptr->ident);476pos += store_gcov_uint32(buffer, pos, fi_ptr->lineno_checksum);477pos += store_gcov_uint32(buffer, pos, fi_ptr->cfg_checksum);478479ci_ptr = fi_ptr->ctrs;480481for (ct_idx = 0; ct_idx < GCOV_COUNTERS; ct_idx++) {482if (!counter_active(info, ct_idx))483continue;484485/* Counter record. */486pos += store_gcov_uint32(buffer, pos,487GCOV_TAG_FOR_COUNTER(ct_idx));488pos += store_gcov_uint32(buffer, pos, ci_ptr->num * 2);489490for (cv_idx = 0; cv_idx < ci_ptr->num; cv_idx++) {491pos += store_gcov_uint64(buffer, pos,492ci_ptr->values[cv_idx]);493}494495ci_ptr++;496}497}498499return (pos);500}501502/**503* gcov_iter_new - allocate and initialize profiling data iterator504* @info: profiling data set to be iterated505*506* Return file iterator on success, %NULL otherwise.507*/508struct gcov_iterator *509gcov_iter_new(struct gcov_info *info)510{511struct gcov_iterator *iter;512513iter = malloc(sizeof(struct gcov_iterator), M_GCOV, M_NOWAIT|M_ZERO);514if (iter == NULL)515goto err_free;516517iter->info = info;518/* Dry-run to get the actual buffer size. */519iter->size = convert_to_gcda(NULL, info);520iter->buffer = malloc(iter->size, M_GCOV, M_NOWAIT);521if (!iter->buffer)522goto err_free;523524convert_to_gcda(iter->buffer, info);525526return iter;527528err_free:529free(iter, M_GCOV);530return (NULL);531}532533534/**535* gcov_iter_get_info - return profiling data set for given file iterator536* @iter: file iterator537*/538void539gcov_iter_free(struct gcov_iterator *iter)540{541free(iter->buffer, M_GCOV);542free(iter, M_GCOV);543}544545/**546* gcov_iter_get_info - return profiling data set for given file iterator547* @iter: file iterator548*/549struct gcov_info *550gcov_iter_get_info(struct gcov_iterator *iter)551{552return (iter->info);553}554555/**556* gcov_iter_start - reset file iterator to starting position557* @iter: file iterator558*/559void560gcov_iter_start(struct gcov_iterator *iter)561{562iter->pos = 0;563}564565/**566* gcov_iter_next - advance file iterator to next logical record567* @iter: file iterator568*569* Return zero if new position is valid, non-zero if iterator has reached end.570*/571int572gcov_iter_next(struct gcov_iterator *iter)573{574if (iter->pos < iter->size)575iter->pos += ITER_STRIDE;576577if (iter->pos >= iter->size)578return (EINVAL);579580return 0;581}582583/**584* gcov_iter_write - write data for current pos to seq_file585* @iter: file iterator586* @seq: seq_file handle587*588* Return zero on success, non-zero otherwise.589*/590int591gcov_iter_write(struct gcov_iterator *iter, struct sbuf *sbuf)592{593size_t len;594595if (iter->pos >= iter->size)596return (EINVAL);597598len = ITER_STRIDE;599if (iter->pos + len > iter->size)600len = iter->size - iter->pos;601602sbuf_bcat(sbuf, iter->buffer + iter->pos, len);603604return (0);605}606607608