123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637 |
- /*===- GCDAProfiling.c - Support library for GCDA file emission -----------===*\
- |*
- |* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- |* See https://llvm.org/LICENSE.txt for license information.
- |* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- |*
- |*===----------------------------------------------------------------------===*|
- |*
- |* This file implements the call back routines for the gcov profiling
- |* instrumentation pass. Link against this library when running code through
- |* the -insert-gcov-profiling LLVM pass.
- |*
- |* We emit files in a corrupt version of GCOV's "gcda" file format. These files
- |* are only close enough that LCOV will happily parse them. Anything that lcov
- |* ignores is missing.
- |*
- |* TODO: gcov is multi-process safe by having each exit open the existing file
- |* and append to it. We'd like to achieve that and be thread-safe too.
- |*
- \*===----------------------------------------------------------------------===*/
- #if !defined(__Fuchsia__)
- #include <errno.h>
- #include <fcntl.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #if defined(_WIN32)
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include "WindowsMMap.h"
- #else
- #include <sys/file.h>
- #include <sys/mman.h>
- #include <sys/types.h>
- #include <unistd.h>
- #endif
- #include "InstrProfiling.h"
- #include "InstrProfilingUtil.h"
- /* #define DEBUG_GCDAPROFILING */
- enum {
- GCOV_DATA_MAGIC = 0x67636461, // "gcda"
- GCOV_TAG_FUNCTION = 0x01000000,
- GCOV_TAG_COUNTER_ARCS = 0x01a10000,
- // GCOV_TAG_OBJECT_SUMMARY superseded GCOV_TAG_PROGRAM_SUMMARY in GCC 9.
- GCOV_TAG_OBJECT_SUMMARY = 0xa1000000,
- GCOV_TAG_PROGRAM_SUMMARY = 0xa3000000,
- };
- /*
- * --- GCOV file format I/O primitives ---
- */
- /*
- * The current file name we're outputting. Used primarily for error logging.
- */
- static char *filename = NULL;
- /*
- * The current file we're outputting.
- */
- static FILE *output_file = NULL;
- /*
- * Buffer that we write things into.
- */
- #define WRITE_BUFFER_SIZE (128 * 1024)
- static unsigned char *write_buffer = NULL;
- static uint64_t cur_buffer_size = 0;
- static uint64_t cur_pos = 0;
- static uint64_t file_size = 0;
- static int new_file = 0;
- static int gcov_version;
- #if defined(_WIN32)
- static HANDLE mmap_handle = NULL;
- #endif
- static int fd = -1;
- typedef void (*fn_ptr)(void);
- typedef void* dynamic_object_id;
- // The address of this variable identifies a given dynamic object.
- static dynamic_object_id current_id;
- #define CURRENT_ID (¤t_id)
- struct fn_node {
- dynamic_object_id id;
- fn_ptr fn;
- struct fn_node* next;
- };
- struct fn_list {
- struct fn_node *head, *tail;
- };
- /*
- * A list of functions to write out the data, shared between all dynamic objects.
- */
- struct fn_list writeout_fn_list;
- /*
- * A list of reset functions, shared between all dynamic objects.
- */
- struct fn_list reset_fn_list;
- static void fn_list_insert(struct fn_list* list, fn_ptr fn) {
- struct fn_node* new_node = malloc(sizeof(struct fn_node));
- new_node->fn = fn;
- new_node->next = NULL;
- new_node->id = CURRENT_ID;
- if (!list->head) {
- list->head = list->tail = new_node;
- } else {
- list->tail->next = new_node;
- list->tail = new_node;
- }
- }
- static void fn_list_remove(struct fn_list* list) {
- struct fn_node* curr = list->head;
- struct fn_node* prev = NULL;
- struct fn_node* next = NULL;
- while (curr) {
- next = curr->next;
- if (curr->id == CURRENT_ID) {
- if (curr == list->head) {
- list->head = next;
- }
- if (curr == list->tail) {
- list->tail = prev;
- }
- if (prev) {
- prev->next = next;
- }
- free(curr);
- } else {
- prev = curr;
- }
- curr = next;
- }
- }
- static void resize_write_buffer(uint64_t size) {
- if (!new_file) return;
- size += cur_pos;
- if (size <= cur_buffer_size) return;
- size = (size - 1) / WRITE_BUFFER_SIZE + 1;
- size *= WRITE_BUFFER_SIZE;
- write_buffer = realloc(write_buffer, size);
- cur_buffer_size = size;
- }
- static void write_bytes(const char *s, size_t len) {
- resize_write_buffer(len);
- memcpy(&write_buffer[cur_pos], s, len);
- cur_pos += len;
- }
- static void write_32bit_value(uint32_t i) {
- write_bytes((char*)&i, 4);
- }
- static void write_64bit_value(uint64_t i) {
- // GCOV uses a lo-/hi-word format even on big-endian systems.
- // See also GCOVBuffer::readInt64 in LLVM.
- uint32_t lo = (uint32_t) i;
- uint32_t hi = (uint32_t) (i >> 32);
- write_32bit_value(lo);
- write_32bit_value(hi);
- }
- static uint32_t read_32bit_value(void) {
- uint32_t val;
- if (new_file)
- return (uint32_t)-1;
- val = *(uint32_t*)&write_buffer[cur_pos];
- cur_pos += 4;
- return val;
- }
- static uint64_t read_64bit_value(void) {
- // GCOV uses a lo-/hi-word format even on big-endian systems.
- // See also GCOVBuffer::readInt64 in LLVM.
- uint32_t lo = read_32bit_value();
- uint32_t hi = read_32bit_value();
- return ((uint64_t)hi << 32) | ((uint64_t)lo);
- }
- static char *mangle_filename(const char *orig_filename) {
- char *new_filename;
- size_t prefix_len;
- int prefix_strip;
- const char *prefix = lprofGetPathPrefix(&prefix_strip, &prefix_len);
- if (prefix == NULL)
- return strdup(orig_filename);
- new_filename = malloc(prefix_len + 1 + strlen(orig_filename) + 1);
- lprofApplyPathPrefix(new_filename, orig_filename, prefix, prefix_len,
- prefix_strip);
- return new_filename;
- }
- static int map_file(void) {
- fseek(output_file, 0L, SEEK_END);
- file_size = ftell(output_file);
- /* A size of 0 means the file has been created just now (possibly by another
- * process in lock-after-open race condition). No need to mmap. */
- if (file_size == 0)
- return -1;
- #if defined(_WIN32)
- HANDLE mmap_fd;
- if (fd == -1)
- mmap_fd = INVALID_HANDLE_VALUE;
- else
- mmap_fd = (HANDLE)_get_osfhandle(fd);
- mmap_handle = CreateFileMapping(mmap_fd, NULL, PAGE_READWRITE, DWORD_HI(file_size), DWORD_LO(file_size), NULL);
- if (mmap_handle == NULL) {
- fprintf(stderr, "profiling: %s: cannot create file mapping: %lu\n",
- filename, GetLastError());
- return -1;
- }
- write_buffer = MapViewOfFile(mmap_handle, FILE_MAP_WRITE, 0, 0, file_size);
- if (write_buffer == NULL) {
- fprintf(stderr, "profiling: %s: cannot map: %lu\n", filename,
- GetLastError());
- CloseHandle(mmap_handle);
- return -1;
- }
- #else
- write_buffer = mmap(0, file_size, PROT_READ | PROT_WRITE,
- MAP_FILE | MAP_SHARED, fd, 0);
- if (write_buffer == (void *)-1) {
- int errnum = errno;
- fprintf(stderr, "profiling: %s: cannot map: %s\n", filename,
- strerror(errnum));
- return -1;
- }
- #endif
- return 0;
- }
- static void unmap_file(void) {
- #if defined(_WIN32)
- if (!UnmapViewOfFile(write_buffer)) {
- fprintf(stderr, "profiling: %s: cannot unmap mapped view: %lu\n", filename,
- GetLastError());
- }
- if (!CloseHandle(mmap_handle)) {
- fprintf(stderr, "profiling: %s: cannot close file mapping handle: %lu\n",
- filename, GetLastError());
- }
- mmap_handle = NULL;
- #else
- if (munmap(write_buffer, file_size) == -1) {
- int errnum = errno;
- fprintf(stderr, "profiling: %s: cannot munmap: %s\n", filename,
- strerror(errnum));
- }
- #endif
- write_buffer = NULL;
- file_size = 0;
- }
- /*
- * --- LLVM line counter API ---
- */
- /* A file in this case is a translation unit. Each .o file built with line
- * profiling enabled will emit to a different file. Only one file may be
- * started at a time.
- */
- COMPILER_RT_VISIBILITY
- void llvm_gcda_start_file(const char *orig_filename, uint32_t version,
- uint32_t checksum) {
- const char *mode = "r+b";
- filename = mangle_filename(orig_filename);
- /* Try just opening the file. */
- fd = open(filename, O_RDWR | O_BINARY);
- if (fd == -1) {
- /* Try creating the file. */
- fd = open(filename, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0644);
- if (fd != -1) {
- mode = "w+b";
- } else {
- /* Try creating the directories first then opening the file. */
- __llvm_profile_recursive_mkdir(filename);
- fd = open(filename, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0644);
- if (fd != -1) {
- mode = "w+b";
- } else {
- /* Another process may have created the file just now.
- * Try opening it without O_CREAT and O_EXCL. */
- fd = open(filename, O_RDWR | O_BINARY);
- if (fd == -1) {
- /* Bah! It's hopeless. */
- int errnum = errno;
- fprintf(stderr, "profiling: %s: cannot open: %s\n", filename,
- strerror(errnum));
- return;
- }
- }
- }
- }
- /* Try to flock the file to serialize concurrent processes writing out to the
- * same GCDA. This can fail if the filesystem doesn't support it, but in that
- * case we'll just carry on with the old racy behaviour and hope for the best.
- */
- lprofLockFd(fd);
- output_file = fdopen(fd, mode);
- /* Initialize the write buffer. */
- new_file = 0;
- write_buffer = NULL;
- cur_buffer_size = 0;
- cur_pos = 0;
- if (map_file() == -1) {
- /* The file has been created just now (file_size == 0) or mmap failed
- * unexpectedly. In the latter case, try to recover by clobbering. */
- new_file = 1;
- write_buffer = NULL;
- resize_write_buffer(WRITE_BUFFER_SIZE);
- memset(write_buffer, 0, WRITE_BUFFER_SIZE);
- }
- /* gcda file, version, stamp checksum. */
- {
- uint8_t c3 = version >> 24;
- uint8_t c2 = (version >> 16) & 255;
- uint8_t c1 = (version >> 8) & 255;
- gcov_version = c3 >= 'A' ? (c3 - 'A') * 100 + (c2 - '0') * 10 + c1 - '0'
- : (c3 - '0') * 10 + c1 - '0';
- }
- write_32bit_value(GCOV_DATA_MAGIC);
- write_32bit_value(version);
- write_32bit_value(checksum);
- #ifdef DEBUG_GCDAPROFILING
- fprintf(stderr, "llvmgcda: [%s]\n", orig_filename);
- #endif
- }
- COMPILER_RT_VISIBILITY
- void llvm_gcda_emit_function(uint32_t ident, uint32_t func_checksum,
- uint32_t cfg_checksum) {
- uint32_t len = 2;
- int use_extra_checksum = gcov_version >= 47;
- if (use_extra_checksum)
- len++;
- #ifdef DEBUG_GCDAPROFILING
- fprintf(stderr, "llvmgcda: function id=0x%08x\n", ident);
- #endif
- if (!output_file) return;
- /* function tag */
- write_32bit_value(GCOV_TAG_FUNCTION);
- write_32bit_value(len);
- write_32bit_value(ident);
- write_32bit_value(func_checksum);
- if (use_extra_checksum)
- write_32bit_value(cfg_checksum);
- }
- COMPILER_RT_VISIBILITY
- void llvm_gcda_emit_arcs(uint32_t num_counters, uint64_t *counters) {
- uint32_t i;
- uint64_t *old_ctrs = NULL;
- uint32_t val = 0;
- uint64_t save_cur_pos = cur_pos;
- if (!output_file) return;
- val = read_32bit_value();
- if (val != (uint32_t)-1) {
- /* There are counters present in the file. Merge them. */
- if (val != GCOV_TAG_COUNTER_ARCS) {
- fprintf(stderr, "profiling: %s: cannot merge previous GCDA file: "
- "corrupt arc tag (0x%08x)\n",
- filename, val);
- return;
- }
- val = read_32bit_value();
- if (val == (uint32_t)-1 || val / 2 != num_counters) {
- fprintf(stderr, "profiling: %s: cannot merge previous GCDA file: "
- "mismatched number of counters (%d)\n",
- filename, val);
- return;
- }
- old_ctrs = malloc(sizeof(uint64_t) * num_counters);
- for (i = 0; i < num_counters; ++i)
- old_ctrs[i] = read_64bit_value();
- }
- cur_pos = save_cur_pos;
- /* Counter #1 (arcs) tag */
- write_32bit_value(GCOV_TAG_COUNTER_ARCS);
- write_32bit_value(num_counters * 2);
- for (i = 0; i < num_counters; ++i) {
- counters[i] += (old_ctrs ? old_ctrs[i] : 0);
- write_64bit_value(counters[i]);
- }
- free(old_ctrs);
- #ifdef DEBUG_GCDAPROFILING
- fprintf(stderr, "llvmgcda: %u arcs\n", num_counters);
- for (i = 0; i < num_counters; ++i)
- fprintf(stderr, "llvmgcda: %llu\n", (unsigned long long)counters[i]);
- #endif
- }
- COMPILER_RT_VISIBILITY
- void llvm_gcda_summary_info(void) {
- uint32_t runs = 1;
- static uint32_t run_counted = 0; // We only want to increase the run count once.
- uint32_t val = 0;
- uint64_t save_cur_pos = cur_pos;
- if (!output_file) return;
- val = read_32bit_value();
- if (val != (uint32_t)-1) {
- /* There are counters present in the file. Merge them. */
- uint32_t gcov_tag =
- gcov_version >= 90 ? GCOV_TAG_OBJECT_SUMMARY : GCOV_TAG_PROGRAM_SUMMARY;
- if (val != gcov_tag) {
- fprintf(stderr,
- "profiling: %s: cannot merge previous run count: "
- "corrupt object tag (0x%08x)\n",
- filename, val);
- return;
- }
- val = read_32bit_value(); /* length */
- uint32_t prev_runs;
- if (gcov_version < 90) {
- read_32bit_value();
- read_32bit_value();
- prev_runs = read_32bit_value();
- } else {
- prev_runs = read_32bit_value();
- read_32bit_value();
- }
- for (uint32_t i = gcov_version < 90 ? 3 : 2; i < val; ++i)
- read_32bit_value();
- /* Add previous run count to new counter, if not already counted before. */
- runs = run_counted ? prev_runs : prev_runs + 1;
- }
- cur_pos = save_cur_pos;
- if (gcov_version >= 90) {
- write_32bit_value(GCOV_TAG_OBJECT_SUMMARY);
- write_32bit_value(2);
- write_32bit_value(runs);
- write_32bit_value(0); // sum_max
- } else {
- // Before gcov 4.8 (r190952), GCOV_TAG_SUMMARY_LENGTH was 9. r190952 set
- // GCOV_TAG_SUMMARY_LENGTH to 22. We simply use the smallest length which
- // can make gcov read "Runs:".
- write_32bit_value(GCOV_TAG_PROGRAM_SUMMARY);
- write_32bit_value(3);
- write_32bit_value(0);
- write_32bit_value(0);
- write_32bit_value(runs);
- }
- run_counted = 1;
- #ifdef DEBUG_GCDAPROFILING
- fprintf(stderr, "llvmgcda: %u runs\n", runs);
- #endif
- }
- COMPILER_RT_VISIBILITY
- void llvm_gcda_end_file(void) {
- /* Write out EOF record. */
- if (output_file) {
- write_bytes("\0\0\0\0\0\0\0\0", 8);
- if (new_file) {
- fwrite(write_buffer, cur_pos, 1, output_file);
- free(write_buffer);
- } else {
- unmap_file();
- }
- fflush(output_file);
- lprofUnlockFd(fd);
- fclose(output_file);
- output_file = NULL;
- write_buffer = NULL;
- }
- free(filename);
- #ifdef DEBUG_GCDAPROFILING
- fprintf(stderr, "llvmgcda: -----\n");
- #endif
- }
- COMPILER_RT_VISIBILITY
- void llvm_register_writeout_function(fn_ptr fn) {
- fn_list_insert(&writeout_fn_list, fn);
- }
- COMPILER_RT_VISIBILITY
- void llvm_writeout_files(void) {
- struct fn_node *curr = writeout_fn_list.head;
- while (curr) {
- if (curr->id == CURRENT_ID) {
- curr->fn();
- }
- curr = curr->next;
- }
- }
- #ifndef _WIN32
- // __attribute__((destructor)) and destructors whose priorities are greater than
- // 100 run before this function and can thus be tracked. The priority is
- // compatible with GCC 7 onwards.
- #if __GNUC__ >= 9
- #pragma GCC diagnostic ignored "-Wprio-ctor-dtor"
- #endif
- __attribute__((destructor(100)))
- #endif
- static void llvm_writeout_and_clear(void) {
- llvm_writeout_files();
- fn_list_remove(&writeout_fn_list);
- }
- COMPILER_RT_VISIBILITY
- void llvm_register_reset_function(fn_ptr fn) {
- fn_list_insert(&reset_fn_list, fn);
- }
- COMPILER_RT_VISIBILITY
- void llvm_delete_reset_function_list(void) { fn_list_remove(&reset_fn_list); }
- COMPILER_RT_VISIBILITY
- void llvm_reset_counters(void) {
- struct fn_node *curr = reset_fn_list.head;
- while (curr) {
- if (curr->id == CURRENT_ID) {
- curr->fn();
- }
- curr = curr->next;
- }
- }
- #if !defined(_WIN32)
- COMPILER_RT_VISIBILITY
- pid_t __gcov_fork() {
- pid_t parent_pid = getpid();
- pid_t pid = fork();
- if (pid == 0) {
- pid_t child_pid = getpid();
- if (child_pid != parent_pid) {
- // The pid changed so we've a fork (one could have its own fork function)
- // Just reset the counters for this child process
- // threads.
- llvm_reset_counters();
- }
- }
- return pid;
- }
- #endif
- COMPILER_RT_VISIBILITY
- void llvm_gcov_init(fn_ptr wfn, fn_ptr rfn) {
- static int atexit_ran = 0;
- if (wfn)
- llvm_register_writeout_function(wfn);
- if (rfn)
- llvm_register_reset_function(rfn);
- if (atexit_ran == 0) {
- atexit_ran = 1;
- /* Make sure we write out the data and delete the data structures. */
- atexit(llvm_delete_reset_function_list);
- #ifdef _WIN32
- atexit(llvm_writeout_and_clear);
- #endif
- }
- }
- void __gcov_dump(void) {
- for (struct fn_node *f = writeout_fn_list.head; f; f = f->next)
- f->fn();
- }
- void __gcov_reset(void) {
- for (struct fn_node *f = reset_fn_list.head; f; f = f->next)
- f->fn();
- }
- #endif
|