Path: blob/main/contrib/llvm-project/compiler-rt/lib/profile/GCDAProfiling.c
35233 views
/*===- GCDAProfiling.c - Support library for GCDA file emission -----------===*\1|*2|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3|* See https://llvm.org/LICENSE.txt for license information.4|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5|*6|*===----------------------------------------------------------------------===*|7|*8|* This file implements the call back routines for the gcov profiling9|* instrumentation pass. Link against this library when running code through10|* the -insert-gcov-profiling LLVM pass.11|*12|* We emit files in a corrupt version of GCOV's "gcda" file format. These files13|* are only close enough that LCOV will happily parse them. Anything that lcov14|* ignores is missing.15|*16|* TODO: gcov is multi-process safe by having each exit open the existing file17|* and append to it. We'd like to achieve that and be thread-safe too.18|*19\*===----------------------------------------------------------------------===*/2021#if !defined(__Fuchsia__)2223#include <errno.h>24#include <fcntl.h>25#include <stdint.h>26#include <stdio.h>27#include <stdlib.h>28#include <string.h>2930#if defined(_WIN32)31#define WIN32_LEAN_AND_MEAN32#include <windows.h>33#include "WindowsMMap.h"34#else35#include <sys/file.h>36#include <sys/mman.h>37#include <sys/types.h>38#include <unistd.h>39#endif4041#include "InstrProfiling.h"42#include "InstrProfilingUtil.h"4344/* #define DEBUG_GCDAPROFILING */4546enum {47GCOV_DATA_MAGIC = 0x67636461, // "gcda"4849GCOV_TAG_FUNCTION = 0x01000000,50GCOV_TAG_COUNTER_ARCS = 0x01a10000,51// GCOV_TAG_OBJECT_SUMMARY superseded GCOV_TAG_PROGRAM_SUMMARY in GCC 9.52GCOV_TAG_OBJECT_SUMMARY = 0xa1000000,53GCOV_TAG_PROGRAM_SUMMARY = 0xa3000000,54};5556/*57* --- GCOV file format I/O primitives ---58*/5960/*61* The current file name we're outputting. Used primarily for error logging.62*/63static char *filename = NULL;6465/*66* The current file we're outputting.67*/68static FILE *output_file = NULL;6970/*71* Buffer that we write things into.72*/73#define WRITE_BUFFER_SIZE (128 * 1024)74static unsigned char *write_buffer = NULL;75static uint64_t cur_buffer_size = 0;76static uint64_t cur_pos = 0;77static uint64_t file_size = 0;78static int new_file = 0;79static int gcov_version;80#if defined(_WIN32)81static HANDLE mmap_handle = NULL;82#endif83static int fd = -1;8485typedef void (*fn_ptr)(void);8687typedef void* dynamic_object_id;88// The address of this variable identifies a given dynamic object.89static dynamic_object_id current_id;90#define CURRENT_ID (¤t_id)9192struct fn_node {93dynamic_object_id id;94fn_ptr fn;95struct fn_node* next;96};9798struct fn_list {99struct fn_node *head, *tail;100};101102/*103* A list of functions to write out the data, shared between all dynamic objects.104*/105struct fn_list writeout_fn_list;106107/*108* A list of reset functions, shared between all dynamic objects.109*/110struct fn_list reset_fn_list;111112static void fn_list_insert(struct fn_list* list, fn_ptr fn) {113struct fn_node* new_node = malloc(sizeof(struct fn_node));114new_node->fn = fn;115new_node->next = NULL;116new_node->id = CURRENT_ID;117118if (!list->head) {119list->head = list->tail = new_node;120} else {121list->tail->next = new_node;122list->tail = new_node;123}124}125126static void fn_list_remove(struct fn_list* list) {127struct fn_node* curr = list->head;128struct fn_node* prev = NULL;129struct fn_node* next = NULL;130131while (curr) {132next = curr->next;133134if (curr->id == CURRENT_ID) {135if (curr == list->head) {136list->head = next;137}138139if (curr == list->tail) {140list->tail = prev;141}142143if (prev) {144prev->next = next;145}146147free(curr);148} else {149prev = curr;150}151152curr = next;153}154}155156static void resize_write_buffer(uint64_t size) {157if (!new_file) return;158size += cur_pos;159if (size <= cur_buffer_size) return;160size = (size - 1) / WRITE_BUFFER_SIZE + 1;161size *= WRITE_BUFFER_SIZE;162write_buffer = realloc(write_buffer, size);163cur_buffer_size = size;164}165166static void write_bytes(const char *s, size_t len) {167resize_write_buffer(len);168memcpy(&write_buffer[cur_pos], s, len);169cur_pos += len;170}171172static void write_32bit_value(uint32_t i) {173write_bytes((char*)&i, 4);174}175176static void write_64bit_value(uint64_t i) {177// GCOV uses a lo-/hi-word format even on big-endian systems.178// See also GCOVBuffer::readInt64 in LLVM.179uint32_t lo = (uint32_t) i;180uint32_t hi = (uint32_t) (i >> 32);181write_32bit_value(lo);182write_32bit_value(hi);183}184185static uint32_t read_32bit_value(void) {186uint32_t val;187188if (new_file)189return (uint32_t)-1;190191val = *(uint32_t*)&write_buffer[cur_pos];192cur_pos += 4;193return val;194}195196static uint64_t read_64bit_value(void) {197// GCOV uses a lo-/hi-word format even on big-endian systems.198// See also GCOVBuffer::readInt64 in LLVM.199uint32_t lo = read_32bit_value();200uint32_t hi = read_32bit_value();201return ((uint64_t)hi << 32) | ((uint64_t)lo);202}203204static char *mangle_filename(const char *orig_filename) {205char *new_filename;206size_t prefix_len;207int prefix_strip;208const char *prefix = lprofGetPathPrefix(&prefix_strip, &prefix_len);209210if (prefix == NULL)211return strdup(orig_filename);212213new_filename = malloc(prefix_len + 1 + strlen(orig_filename) + 1);214lprofApplyPathPrefix(new_filename, orig_filename, prefix, prefix_len,215prefix_strip);216217return new_filename;218}219220static int map_file(void) {221fseek(output_file, 0L, SEEK_END);222file_size = ftell(output_file);223224/* A size of 0 means the file has been created just now (possibly by another225* process in lock-after-open race condition). No need to mmap. */226if (file_size == 0)227return -1;228229#if defined(_WIN32)230HANDLE mmap_fd;231if (fd == -1)232mmap_fd = INVALID_HANDLE_VALUE;233else234mmap_fd = (HANDLE)_get_osfhandle(fd);235236mmap_handle = CreateFileMapping(mmap_fd, NULL, PAGE_READWRITE, DWORD_HI(file_size), DWORD_LO(file_size), NULL);237if (mmap_handle == NULL) {238fprintf(stderr, "profiling: %s: cannot create file mapping: %lu\n",239filename, GetLastError());240return -1;241}242243write_buffer = MapViewOfFile(mmap_handle, FILE_MAP_WRITE, 0, 0, file_size);244if (write_buffer == NULL) {245fprintf(stderr, "profiling: %s: cannot map: %lu\n", filename,246GetLastError());247CloseHandle(mmap_handle);248return -1;249}250#else251write_buffer = mmap(0, file_size, PROT_READ | PROT_WRITE,252MAP_FILE | MAP_SHARED, fd, 0);253if (write_buffer == (void *)-1) {254int errnum = errno;255fprintf(stderr, "profiling: %s: cannot map: %s\n", filename,256strerror(errnum));257return -1;258}259#endif260261return 0;262}263264static void unmap_file(void) {265#if defined(_WIN32)266if (!UnmapViewOfFile(write_buffer)) {267fprintf(stderr, "profiling: %s: cannot unmap mapped view: %lu\n", filename,268GetLastError());269}270271if (!CloseHandle(mmap_handle)) {272fprintf(stderr, "profiling: %s: cannot close file mapping handle: %lu\n",273filename, GetLastError());274}275276mmap_handle = NULL;277#else278if (munmap(write_buffer, file_size) == -1) {279int errnum = errno;280fprintf(stderr, "profiling: %s: cannot munmap: %s\n", filename,281strerror(errnum));282}283#endif284285write_buffer = NULL;286file_size = 0;287}288289/*290* --- LLVM line counter API ---291*/292293/* A file in this case is a translation unit. Each .o file built with line294* profiling enabled will emit to a different file. Only one file may be295* started at a time.296*/297COMPILER_RT_VISIBILITY298void llvm_gcda_start_file(const char *orig_filename, uint32_t version,299uint32_t checksum) {300const char *mode = "r+b";301filename = mangle_filename(orig_filename);302303/* Try just opening the file. */304fd = open(filename, O_RDWR | O_BINARY);305306if (fd == -1) {307/* Try creating the file. */308fd = open(filename, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0644);309if (fd != -1) {310mode = "w+b";311} else {312/* Try creating the directories first then opening the file. */313__llvm_profile_recursive_mkdir(filename);314fd = open(filename, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0644);315if (fd != -1) {316mode = "w+b";317} else {318/* Another process may have created the file just now.319* Try opening it without O_CREAT and O_EXCL. */320fd = open(filename, O_RDWR | O_BINARY);321if (fd == -1) {322/* Bah! It's hopeless. */323int errnum = errno;324fprintf(stderr, "profiling: %s: cannot open: %s\n", filename,325strerror(errnum));326return;327}328}329}330}331332/* Try to flock the file to serialize concurrent processes writing out to the333* same GCDA. This can fail if the filesystem doesn't support it, but in that334* case we'll just carry on with the old racy behaviour and hope for the best.335*/336lprofLockFd(fd);337output_file = fdopen(fd, mode);338339/* Initialize the write buffer. */340new_file = 0;341write_buffer = NULL;342cur_buffer_size = 0;343cur_pos = 0;344345if (map_file() == -1) {346/* The file has been created just now (file_size == 0) or mmap failed347* unexpectedly. In the latter case, try to recover by clobbering. */348new_file = 1;349write_buffer = NULL;350resize_write_buffer(WRITE_BUFFER_SIZE);351memset(write_buffer, 0, WRITE_BUFFER_SIZE);352}353354/* gcda file, version, stamp checksum. */355{356uint8_t c3 = version >> 24;357uint8_t c2 = (version >> 16) & 255;358uint8_t c1 = (version >> 8) & 255;359gcov_version = c3 >= 'A' ? (c3 - 'A') * 100 + (c2 - '0') * 10 + c1 - '0'360: (c3 - '0') * 10 + c1 - '0';361}362write_32bit_value(GCOV_DATA_MAGIC);363write_32bit_value(version);364write_32bit_value(checksum);365366#ifdef DEBUG_GCDAPROFILING367fprintf(stderr, "llvmgcda: [%s]\n", orig_filename);368#endif369}370371COMPILER_RT_VISIBILITY372void llvm_gcda_emit_function(uint32_t ident, uint32_t func_checksum,373uint32_t cfg_checksum) {374uint32_t len = 2;375int use_extra_checksum = gcov_version >= 47;376377if (use_extra_checksum)378len++;379#ifdef DEBUG_GCDAPROFILING380fprintf(stderr, "llvmgcda: function id=0x%08x\n", ident);381#endif382if (!output_file) return;383384/* function tag */385write_32bit_value(GCOV_TAG_FUNCTION);386write_32bit_value(len);387write_32bit_value(ident);388write_32bit_value(func_checksum);389if (use_extra_checksum)390write_32bit_value(cfg_checksum);391}392393COMPILER_RT_VISIBILITY394void llvm_gcda_emit_arcs(uint32_t num_counters, uint64_t *counters) {395uint32_t i;396uint64_t *old_ctrs = NULL;397uint32_t val = 0;398uint64_t save_cur_pos = cur_pos;399400if (!output_file) return;401402val = read_32bit_value();403404if (val != (uint32_t)-1) {405/* There are counters present in the file. Merge them. */406if (val != GCOV_TAG_COUNTER_ARCS) {407fprintf(stderr, "profiling: %s: cannot merge previous GCDA file: "408"corrupt arc tag (0x%08x)\n",409filename, val);410return;411}412413val = read_32bit_value();414if (val == (uint32_t)-1 || val / 2 != num_counters) {415fprintf(stderr, "profiling: %s: cannot merge previous GCDA file: "416"mismatched number of counters (%d)\n",417filename, val);418return;419}420421old_ctrs = malloc(sizeof(uint64_t) * num_counters);422for (i = 0; i < num_counters; ++i)423old_ctrs[i] = read_64bit_value();424}425426cur_pos = save_cur_pos;427428/* Counter #1 (arcs) tag */429write_32bit_value(GCOV_TAG_COUNTER_ARCS);430write_32bit_value(num_counters * 2);431for (i = 0; i < num_counters; ++i) {432counters[i] += (old_ctrs ? old_ctrs[i] : 0);433write_64bit_value(counters[i]);434}435436free(old_ctrs);437438#ifdef DEBUG_GCDAPROFILING439fprintf(stderr, "llvmgcda: %u arcs\n", num_counters);440for (i = 0; i < num_counters; ++i)441fprintf(stderr, "llvmgcda: %llu\n", (unsigned long long)counters[i]);442#endif443}444445COMPILER_RT_VISIBILITY446void llvm_gcda_summary_info(void) {447uint32_t runs = 1;448static uint32_t run_counted = 0; // We only want to increase the run count once.449uint32_t val = 0;450uint64_t save_cur_pos = cur_pos;451452if (!output_file) return;453454val = read_32bit_value();455456if (val != (uint32_t)-1) {457/* There are counters present in the file. Merge them. */458uint32_t gcov_tag =459gcov_version >= 90 ? GCOV_TAG_OBJECT_SUMMARY : GCOV_TAG_PROGRAM_SUMMARY;460if (val != gcov_tag) {461fprintf(stderr,462"profiling: %s: cannot merge previous run count: "463"corrupt object tag (0x%08x)\n",464filename, val);465return;466}467468val = read_32bit_value(); /* length */469uint32_t prev_runs;470if (gcov_version < 90) {471read_32bit_value();472read_32bit_value();473prev_runs = read_32bit_value();474} else {475prev_runs = read_32bit_value();476read_32bit_value();477}478for (uint32_t i = gcov_version < 90 ? 3 : 2; i < val; ++i)479read_32bit_value();480/* Add previous run count to new counter, if not already counted before. */481runs = run_counted ? prev_runs : prev_runs + 1;482}483484cur_pos = save_cur_pos;485486if (gcov_version >= 90) {487write_32bit_value(GCOV_TAG_OBJECT_SUMMARY);488write_32bit_value(2);489write_32bit_value(runs);490write_32bit_value(0); // sum_max491} else {492// Before gcov 4.8 (r190952), GCOV_TAG_SUMMARY_LENGTH was 9. r190952 set493// GCOV_TAG_SUMMARY_LENGTH to 22. We simply use the smallest length which494// can make gcov read "Runs:".495write_32bit_value(GCOV_TAG_PROGRAM_SUMMARY);496write_32bit_value(3);497write_32bit_value(0);498write_32bit_value(0);499write_32bit_value(runs);500}501502run_counted = 1;503504#ifdef DEBUG_GCDAPROFILING505fprintf(stderr, "llvmgcda: %u runs\n", runs);506#endif507}508509COMPILER_RT_VISIBILITY510void llvm_gcda_end_file(void) {511/* Write out EOF record. */512if (output_file) {513write_bytes("\0\0\0\0\0\0\0\0", 8);514515if (new_file) {516fwrite(write_buffer, cur_pos, 1, output_file);517free(write_buffer);518} else {519unmap_file();520}521522fflush(output_file);523lprofUnlockFd(fd);524fclose(output_file);525output_file = NULL;526write_buffer = NULL;527}528free(filename);529530#ifdef DEBUG_GCDAPROFILING531fprintf(stderr, "llvmgcda: -----\n");532#endif533}534535COMPILER_RT_VISIBILITY536void llvm_register_writeout_function(fn_ptr fn) {537fn_list_insert(&writeout_fn_list, fn);538}539540COMPILER_RT_VISIBILITY541void llvm_writeout_files(void) {542struct fn_node *curr = writeout_fn_list.head;543544while (curr) {545if (curr->id == CURRENT_ID) {546curr->fn();547}548curr = curr->next;549}550}551552#ifndef _WIN32553// __attribute__((destructor)) and destructors whose priorities are greater than554// 100 run before this function and can thus be tracked. The priority is555// compatible with GCC 7 onwards.556#if __GNUC__ >= 9557#pragma GCC diagnostic ignored "-Wprio-ctor-dtor"558#endif559__attribute__((destructor(100)))560#endif561static void llvm_writeout_and_clear(void) {562llvm_writeout_files();563fn_list_remove(&writeout_fn_list);564}565566COMPILER_RT_VISIBILITY567void llvm_register_reset_function(fn_ptr fn) {568fn_list_insert(&reset_fn_list, fn);569}570571COMPILER_RT_VISIBILITY572void llvm_delete_reset_function_list(void) { fn_list_remove(&reset_fn_list); }573574COMPILER_RT_VISIBILITY575void llvm_reset_counters(void) {576struct fn_node *curr = reset_fn_list.head;577578while (curr) {579if (curr->id == CURRENT_ID) {580curr->fn();581}582curr = curr->next;583}584}585586#if !defined(_WIN32)587COMPILER_RT_VISIBILITY588pid_t __gcov_fork() {589pid_t parent_pid = getpid();590pid_t pid = fork();591592if (pid == 0) {593pid_t child_pid = getpid();594if (child_pid != parent_pid) {595// The pid changed so we've a fork (one could have its own fork function)596// Just reset the counters for this child process597// threads.598llvm_reset_counters();599}600}601return pid;602}603#endif604605COMPILER_RT_VISIBILITY606void llvm_gcov_init(fn_ptr wfn, fn_ptr rfn) {607static int atexit_ran = 0;608609if (wfn)610llvm_register_writeout_function(wfn);611612if (rfn)613llvm_register_reset_function(rfn);614615if (atexit_ran == 0) {616atexit_ran = 1;617618/* Make sure we write out the data and delete the data structures. */619atexit(llvm_delete_reset_function_list);620#ifdef _WIN32621atexit(llvm_writeout_and_clear);622#endif623}624}625626void __gcov_dump(void) {627for (struct fn_node *f = writeout_fn_list.head; f; f = f->next)628f->fn();629}630631void __gcov_reset(void) {632for (struct fn_node *f = reset_fn_list.head; f; f = f->next)633f->fn();634}635636#endif637638639