*.so.*
*.d
.depend
+*.gcno
+*.gcda
TAGS
tags
GPATH
{
FILE *fd = NULL;
int server_nr, command, size, result;
- char buff[BUFF_SZ]; /* Buffer for all the metadata and file data sent */
+ static char buff[BUFF_SZ]; /* Buffer for all the metadata and file data */
- if(argc!=2 || sscanf(argv[1], "%d", &server_nr)!=1) {
- fprintf(stderr, "Usage: %s <pid>\n", argv[0]);
- return 1;
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <label>\n", argv[0]);
+ return 1;
}
/*
the <minix/gcov.h> header file.
*/
- /* visit complete buffer, so vm won't has to
+ /* Fault in the complete buffer, so vm won't have to
manage the pages while flushing
*/
- memset(buff, 'a', sizeof(buff));
+ memset(buff, '\0', sizeof(buff));
buff_p = buff;
- result = gcov_flush_svr(buff_p, BUFF_SZ, server_nr);
+ result = gcov_flush_svr(argv[1], buff_p, BUFF_SZ);
if(result >= BUFF_SZ) {
fprintf(stderr, "Too much data to hold in buffer: %d\n", result);
+#ifndef _MINIX_GCOV_H
+#define _MINIX_GCOV_H
+
#include <sys/types.h>
#include <lib.h>
#include <stdlib.h>
/* More information on the GCOV Minix Wiki page. */
-int gcov_flush_svr(char *buff, int buff_sz, int server_nr);
-extern void __gcov_flush (void);
+int gcov_flush_svr(const char * label, char * buff, size_t buff_sz);
+
+#if _MINIX_SYSTEM
+extern void __gcov_flush(void);
int do_gcov_flush_impl(message *msg);
-FILE *_gcov_fopen(char *name, char *mode);
+FILE *_gcov_fopen(const char *name, const char *mode);
size_t _gcov_fread(void *ptr, size_t itemsize, size_t nitems,
FILE *stream);
-size_t _gcov_fwrite(void *ptr, size_t itemsize, size_t nitems,
+size_t _gcov_fwrite(const void *ptr, size_t itemsize, size_t nitems,
FILE *stream);
int _gcov_fclose(FILE *stream);
int _gcov_fseek(FILE *stream, long offset, int ptrname);
char *_gcov_getenv(const char *name);
+#endif /* _MINIX_SYSTEM */
+
+#endif /* !_MINIX_GCOV_H */
_ASSERT_MSG_SIZE(mess_lc_vfs_fsync);
typedef struct {
- cp_grant_id_t grant;
- int pid;
- vir_bytes buff_p;
- size_t buff_sz;
+ size_t labellen;
+ size_t buflen;
+ vir_bytes label;
+ vir_bytes buf;
uint8_t padding[40];
} mess_lc_vfs_gcov;
} mess_vfs_lchardriver_select;
_ASSERT_MSG_SIZE(mess_vfs_lchardriver_select);
+typedef struct {
+ cp_grant_id_t grant;
+ size_t size;
+
+ uint8_t padding[48];
+} mess_vfs_lsys_gcov;
+_ASSERT_MSG_SIZE(mess_vfs_lsys_gcov);
+
typedef struct {
time_t atime;
time_t mtime;
mess_vfs_lchardriver_openclose m_vfs_lchardriver_openclose;
mess_vfs_lchardriver_readwrite m_vfs_lchardriver_readwrite;
mess_vfs_lchardriver_select m_vfs_lchardriver_select;
+ mess_vfs_lsys_gcov m_vfs_lsys_gcov;
mess_vfs_utimens m_vfs_utimens;
mess_vm_vfs_mmap m_vm_vfs_mmap;
mess_vmmcp m_vmmcp;
typedef struct util_timingdata util_timingdata_t;
+/* GCOV support (not always compiled in) */
+int gcov_flush(endpoint_t endpt, cp_grant_id_t grant, size_t bufsize);
+
#endif /* _MINIX_SYSUTIL_H */
# Disable magic and ASR passes for the kernel.
USE_MAGIC=no
+# Disable coverage profiling for the kernel, at least for now.
+MKCOVERAGE=no
+
.include <minix.service.mk>
#include <stdio.h>
#include <stdlib.h>
+#define _MINIX_SYSTEM 1
#include <minix/gcov.h>
/* wrappers for file system calls from gcc libgcov library.
implementation for servers is used.
*/
-FILE *_gcov_fopen(char *name, char *mode){
+FILE *_gcov_fopen(const char *name, const char *mode){
return fopen(name, mode);
}
return fread(ptr, itemsize, nitems, stream);
}
-size_t _gcov_fwrite(void *ptr, size_t itemsize, size_t nitems
+size_t _gcov_fwrite(const void *ptr, size_t itemsize, size_t nitems
, FILE *stream){
return fwrite(ptr, itemsize, nitems, stream);
}
#include <string.h>
#include <minix/gcov.h>
-int gcov_flush_svr(char *buff, int buff_sz, int server_nr)
+int
+gcov_flush_svr(const char * label, char * buff, size_t buff_sz)
{
message m;
memset(&m, 0, sizeof(m));
- m.m_lc_vfs_gcov.buff_p = (vir_bytes)buff;
- m.m_lc_vfs_gcov.buff_sz = buff_sz;
- m.m_lc_vfs_gcov.pid = server_nr;
+ m.m_lc_vfs_gcov.label = (vir_bytes)label;
+ m.m_lc_vfs_gcov.labellen = strlen(label) + 1;
+ m.m_lc_vfs_gcov.buf = (vir_bytes)buff;
+ m.m_lc_vfs_gcov.buflen = buff_sz;
- /* Make the call to server. It will call the gcov library,
- * buffer the stdio requests, and copy the buffer to this user
- * space
+ /*
+ * Make the call to VFS. VFS will call the gcov library, buffer the
+ * stdio requests, and copy the buffer to us.
*/
return _syscall(VFS_PROC_NR, VFS_GCOV_FLUSH, &m);
}
.if ${MKCOVERAGE} != "no"
SRCS+= gcov.c \
- sef_gcov.c
+ sef_gcov.c \
+ llvm_gcov.c
CPPFLAGS+= -DUSE_COVERAGE
.endif
#include <unistd.h>
#include <assert.h>
#include <minix/syslib.h>
+#include <minix/sysutil.h>
#include <minix/gcov.h>
-static int grant, pos; /* data-buffer pointer from user space tool */
+static endpoint_t endpt = NONE; /* endpoint of requesting service, or NONE */
+static cp_grant_id_t grant; /* grant to write results into during request */
+static int pos; /* data-buffer pointer from user space tool */
static int gcov_enable=0; /* nothing will be done with gcov-data if zero */
static int gcov_buff_sz; /* size of user space buffer */
static FILE gcov_file; /* used as fopen() return value. */
static int gcov_opened;
/* copies <size> bytes from <ptr> to <gcov_buff> */
-static void add_buff(void *ptr, int size)
+static void add_buff(const void *ptr, int size)
{
int r;
+
+ assert(endpt != NONE);
assert(pos <= gcov_buff_sz);
if(pos+size > gcov_buff_sz) {
size = pos - gcov_buff_sz;
}
- r = sys_safecopyto(VFS_PROC_NR, grant, pos, (vir_bytes)ptr, size);
+ r = sys_safecopyto(endpt, grant, pos, (vir_bytes)ptr, size);
if(r) {
printf("libsys: gcov: safecopy failed (%d)\n", r);
* system calls (fopen, etc)
*/
-FILE *_gcov_fopen(char *name, char *mode)
+FILE *_gcov_fopen(const char *name, const char *mode)
{
if(!gcov_enable) return NULL;
return 0;
}
-size_t _gcov_fwrite(void *ptr, size_t itemsize, size_t nitems, FILE *stream)
+size_t _gcov_fwrite(const void *ptr, size_t itemsize, size_t nitems,
+ FILE *stream)
{
int size = itemsize * nitems;
return NULL;
}
-int gcov_flush(cp_grant_id_t grantid, int bufsize)
+int gcov_flush(endpoint_t ep, cp_grant_id_t grantid, size_t bufsize)
{
/* Initialize global state. */
pos=0;
+ endpt = ep;
grant = grantid;
gcov_buff_sz = bufsize;
assert(!gcov_enable);
assert(!gcov_opened);
assert(gcov_enable);
gcov_enable = 0;
+ endpt = NONE;
/* Return number of bytes used in buffer. */
return pos;
memset(&replymsg, 0, sizeof(replymsg));
assert(msg->m_type == COMMON_REQ_GCOV_DATA);
- assert(msg->m_source == VFS_PROC_NR);
- replymsg.m_type = gcov_flush(msg->m_lc_vfs_gcov.grant,
- msg->m_lc_vfs_gcov.buff_sz);
+ replymsg.m_type = gcov_flush(msg->m_source, msg->m_vfs_lsys_gcov.grant,
+ msg->m_vfs_lsys_gcov.size);
return ipc_send(msg->m_source, &replymsg);
}
-
--- /dev/null
+/* LLVM-to-GCOV converter by D.C. van Moolenbroek */
+/*
+ * Originally, we had a GCOV code coverage implementation for GCC only. We
+ * have now largely switched to LLVM, and LLVM uses a different internal
+ * implementation of the coverage data generation. For regular userland
+ * programs, the implementation is part of LLVM compiler-rt's libprofile_rt.
+ * That implementation is unsuitable for our system services. Instead, this
+ * file converts the calls used by LLVM into _gcov_f*() calls expected by our
+ * GCOV-for-GCC implementation, thus adding support for LLVM coverage by
+ * leveraging our previous GCC support.
+ */
+
+#if __clang__
+
+#include <stdlib.h>
+#include <string.h>
+#include <minix/syslib.h>
+#include <minix/sysutil.h>
+#include <minix/gcov.h>
+#include <assert.h>
+
+/*
+ * What is the maximum number of source modules for one single system service?
+ * This number is currently way higher than needed, but if we ever add support
+ * for coverage of system service libraries (e.g., libsys and libminc), this
+ * number may not even be high enough. A warning is printed on overflow.
+ * Note that we need this to be a static array, because we cannot use malloc()
+ * in particular in the initialization stage of the VM service.
+ */
+#define NR_MODULES 256
+
+/*
+ * The code in this file is a MINIX3 service specific replacement of the
+ * GCDAProfiling.c code in LLVM's compiler-rt. Their code cannot be used
+ * directly because they assume a userland environment, using all sorts of
+ * POSIX calls as well as malloc(3), none of which we can offer for system
+ * services in this case. So, we provide our own implementation instead.
+ * However, while compiler-rt is always kept in sync with the LLVM profiling
+ * data emitter, we do not have that luxury. The current version of this
+ * implementation has been written for LLVM 3.4 and 3.6, between which the LLVM
+ * GCOV ABI changed. Our current implementation supports both versions, but
+ * may break with newer LLVM versions, even though we should be good up to and
+ * possibly including LLVM 4.0 at least. Hopefully, at this point the LLVM
+ * GCOV ABI should have stabilized a bit.
+ *
+ * Note that since we do not have access to internal LLVM headers here, an ABI
+ * mismatch would not be noticable until llvm-cov fails to load the resulting
+ * files. This whole mess is worth it only because we can really, really use
+ * the coverage information for our test sets..
+ */
+#if __clang_major__ == 3 && __clang_minor__ == 4
+#define LLVM_35 0 /* version 3.4 only */
+#elif __clang_major__ == 3 && __clang_minor__ >= 5
+#define LLVM_35 1 /* version 3.5 and later */
+#else
+#error "unknown LLVM/clang version, manual inspection required"
+#endif
+
+typedef void (*write_cb_t)(void);
+typedef void (*flush_cb_t)(void);
+
+/*
+ * Except for llvm_gcda_emit_function(), these functions are already declared
+ * in the 3.5+ ABI style. With the 3.4 ABI, some parameters may have garbage.
+ */
+void llvm_gcda_start_file(const char *, const char *, uint32_t);
+void llvm_gcda_emit_function(uint32_t, const char *,
+#if LLVM_35
+ uint32_t, uint8_t, uint32_t);
+#else
+ uint8_t);
+#endif
+void llvm_gcda_emit_arcs(uint32_t, uint64_t *);
+void llvm_gcda_summary_info(void);
+void llvm_gcda_end_file(void);
+void __gcov_flush(void);
+void llvm_gcov_init(write_cb_t, flush_cb_t);
+
+static flush_cb_t flush_array[NR_MODULES];
+static unsigned int flush_count = 0;
+
+static FILE *gcov_file = NULL;
+
+/*
+ * LLVM hook for opening the .gcda file for a specific source module.
+ */
+void
+llvm_gcda_start_file(const char * file_name, const char version[4],
+ uint32_t stamp)
+{
+ uint32_t word[3];
+
+ assert(gcov_file == NULL);
+
+ gcov_file = _gcov_fopen(file_name, "w+b");
+ assert(gcov_file != NULL);
+
+ /*
+ * Each _gcov_fwrite() invocation translates into a kernel call, so we
+ * want to aggregate writes as much as possible.
+ */
+ word[0] = 0x67636461; /* magic: "gcda" */
+ memcpy(&word[1], version, sizeof(word[1])); /* version */
+#if LLVM_35
+ word[2] = stamp; /* stamp */
+#else
+ word[2] = 0x4C4C564D; /* stamp: "LLVM" */
+#endif
+
+ _gcov_fwrite(word, sizeof(word[0]), __arraycount(word), gcov_file);
+}
+
+/*
+ * LLVM hook for writing a function announcement to the currently opened .gcda
+ * file.
+ */
+void
+llvm_gcda_emit_function(uint32_t ident, const char * func_name,
+#if LLVM_35
+ uint32_t func_cksum, uint8_t extra_cksum, uint32_t cfg_cksum)
+#else
+ uint8_t extra_cksum)
+#endif
+{
+ uint32_t word[6];
+ size_t words, len, wlen;
+
+ word[0] = 0x01000000; /* tag: function */
+ words = 2;
+ word[2] = ident; /* ident */
+#if LLVM_35
+ word[3] = func_cksum; /* function checksum */
+#else
+ word[3] = 0; /* function checksum */
+#endif
+ if (extra_cksum) {
+#if LLVM_35
+ word[4] = cfg_cksum; /* configuration checksum */
+#else
+ word[4] = 0; /* configuration checksum */
+#endif
+ words++;
+ }
+ word[1] = words; /* length */
+
+ if (func_name != NULL) {
+ len = strlen(func_name) + 1;
+ wlen = len / sizeof(word[0]) + 1;
+ word[1] += 1 + wlen;
+ word[2 + words] = wlen;
+ words++;
+ }
+
+ _gcov_fwrite(word, sizeof(word[0]), 2 + words, gcov_file);
+
+ if (func_name != NULL) {
+ _gcov_fwrite(func_name, 1, len, gcov_file);
+ _gcov_fwrite("\0\0\0\0", 1, wlen * sizeof(uint32_t) - len,
+ gcov_file);
+ }
+}
+
+/*
+ * LLVM hook for writing function arc counters to the currently opened .gcda
+ * file.
+ */
+void
+llvm_gcda_emit_arcs(uint32_t ncounters, uint64_t * counters)
+{
+ uint32_t word[2];
+
+ assert(gcov_file != NULL);
+
+ word[0] = 0x01a10000; /* tag: arc counters */
+ word[1] = ncounters * 2; /* length */
+
+ _gcov_fwrite(word, sizeof(word[0]), __arraycount(word), gcov_file);
+ _gcov_fwrite(counters, sizeof(*counters), ncounters, gcov_file);
+}
+
+/*
+ * LLVM hook for writing summary information to the currently opened .gcda
+ * file.
+ */
+void
+llvm_gcda_summary_info(void)
+{
+ uint32_t word[13];
+
+ memset(word, 0, sizeof(word));
+ word[0] = 0xa1000000; /* tag: object summary */
+ word[1] = 9; /* length */
+ word[2] = 0; /* checksum */
+ word[3] = 0; /* counter number */
+ word[4] = 1; /* runs */
+ word[11] = 0xa3000000; /* tag: program summary */
+ word[12] = 0; /* length */
+
+ _gcov_fwrite(word, sizeof(word[0]), __arraycount(word), gcov_file);
+}
+
+/*
+ * LLVM hook for closing the currently opened .gcda file.
+ */
+void
+llvm_gcda_end_file(void)
+{
+ uint32_t word[2];
+
+ assert(gcov_file != NULL);
+
+ word[0] = 0; /* tag: end of file */
+ word[1] = 0; /* length zero */
+ _gcov_fwrite(word, sizeof(word[0]), __arraycount(word), gcov_file);
+
+ _gcov_fclose(gcov_file);
+ gcov_file = NULL;
+}
+
+/*
+ * Our implementation for LLVM of the GCC function to flush the coverage data.
+ * The function is called by our libsys's GCOV code.
+ */
+void
+__gcov_flush(void)
+{
+ unsigned int i;
+
+ /* Call the flush function for each registered module. */
+ for (i = 0; i < flush_count; i++)
+ flush_array[i]();
+}
+
+/*
+ * LLVM hook for registration of write and flush callbacks. The former is to
+ * be used on exit, the latter on a pre-exit flush. We use the latter only.
+ * This function is basically called once for each compiled source module.
+ */
+void
+llvm_gcov_init(write_cb_t write_cb __unused, flush_cb_t flush_cb)
+{
+
+ if (flush_cb == NULL)
+ return;
+
+ /* If the array is full, drop this module. */
+ if (flush_count == __arraycount(flush_array))
+ return; /* array full, so we are going to miss information */
+
+ /* Add the flush function to the array. */
+ flush_array[flush_count++] = flush_cb;
+
+ /*
+ * We print this warning here so that we print it only once. What are
+ * the odds that there are *exactly* NR_MODULES modules anyway?
+ */
+ if (flush_count == __arraycount(flush_array))
+ printf("llvm_gcov: process %d has too many modules, "
+ "profiling data lost\n", sef_self());
+}
+
+#endif /*__clang__*/
static sef_cb_gcov_t sef_cb_gcov = SEF_CB_GCOV_FLUSH_DEFAULT;
+int do_sef_gcov_request(message *);
+
/*===========================================================================*
* do_sef_gcov_request *
*===========================================================================*/
+#include <string.h>
+
#include "fs.h"
#include "file.h"
-int gcov_flush(cp_grant_id_t grantid, size_t size );
-
/*===========================================================================*
- * do_gcov_flush *
+ * do_gcov_flush *
*===========================================================================*/
-int do_gcov_flush()
+int do_gcov_flush(void)
{
/* A userland tool has requested the gcov data from another
* process (possibly vfs itself). Grant the target process
* makes the target copy its buffer to the caller (incl vfs
* itself).
*/
- struct fproc *rfp;
- ssize_t size;
+ char label[LABEL_MAX];
+ vir_bytes labeladdr, buf;
+ size_t labellen, size;
+ endpoint_t endpt;
cp_grant_id_t grantid;
- int r, n;
- pid_t target;
+ int r;
message m;
- vir_bytes buf;
-
- size = job_m_in.m_lc_vfs_gcov.buff_sz;
- target = job_m_in.m_lc_vfs_gcov.pid;
- buf = job_m_in.m_lc_vfs_gcov.buff_p;
- /* If the wrong process is sent to, the system hangs; so make this root-only.
+ /*
+ * Something as sensitive as system service coverage information must be
+ * call to the target service, and so it is not impossible to deadlock the
+ * system with this call.
*/
-
if (!super_user) return(EPERM);
- /* Find target gcov process. */
- for(n = 0; n < NR_PROCS; n++) {
- if(fproc[n].fp_endpoint != NONE && fproc[n].fp_pid == target)
- break;
- }
- if(n >= NR_PROCS) {
- printf("VFS: gcov process %d not found\n", target);
- return(ESRCH);
- }
- rfp = &fproc[n];
+ labeladdr = job_m_in.m_lc_vfs_gcov.label;
+ labellen = job_m_in.m_lc_vfs_gcov.labellen;
+ buf = job_m_in.m_lc_vfs_gcov.buf;
+ size = job_m_in.m_lc_vfs_gcov.buflen;
+
+ /* Retrieve and look up the target label. */
+ if (labellen >= sizeof(label))
+ return EINVAL;
+ if ((r = sys_datacopy_wrapper(who_e, labeladdr, SELF, (vir_bytes)label,
+ labellen)) != OK)
+ return r;
+ label[labellen - 1] = '\0';
+
+ if ((r = ds_retrieve_label_endpt(label, &endpt)) != OK)
+ return r;
+
+ /* Hack: init is the only non-system process with a valid label. */
+ if (endpt == INIT_PROC_NR)
+ return ENOENT;
/* Grant target process to requestor's buffer. */
- if ((grantid = cpf_grant_magic(rfp->fp_endpoint, who_e, buf,
- size, CPF_WRITE)) < 0) {
+ if ((grantid = cpf_grant_magic(endpt, who_e, buf, size, CPF_WRITE)) < 0) {
printf("VFS: gcov_flush: grant failed\n");
return(ENOMEM);
}
- if (rfp->fp_endpoint == VFS_PROC_NR) {
+ if (endpt == VFS_PROC_NR) {
/* Request is for VFS itself. */
- r = gcov_flush(grantid, size);
+ r = gcov_flush(VFS_PROC_NR, grantid, size);
} else {
/* Perform generic GCOV request. */
- m.m_lc_vfs_gcov.grant = grantid;
- m.m_lc_vfs_gcov.buff_sz = size;
- r = _taskcall(rfp->fp_endpoint, COMMON_REQ_GCOV_DATA, &m);
+ memset(&m, 0, sizeof(m));
+ m.m_vfs_lsys_gcov.grant = grantid;
+ m.m_vfs_lsys_gcov.size = size;
+ r = _taskcall(endpt, COMMON_REQ_GCOV_DATA, &m);
}
cpf_revoke(grantid);
vfs_gcov_flush_out(struct trace_proc * proc, const message * m_out)
{
- put_ptr(proc, "buff", m_out->m_lc_vfs_gcov.buff_p);
- put_value(proc, "buff_sz", "%zu", m_out->m_lc_vfs_gcov.buff_sz);
- put_value(proc, "server_pid", "%d", m_out->m_lc_vfs_gcov.pid);
+ put_buf(proc, "label", PF_STRING, m_out->m_lc_vfs_gcov.label,
+ m_out->m_lc_vfs_gcov.labellen);
+ put_ptr(proc, "buff", m_out->m_lc_vfs_gcov.buf);
+ put_value(proc, "buff_sz", "%zu", m_out->m_lc_vfs_gcov.buflen);
return CT_DONE;
}
LCOV=lcov.$(PROG)
CLEANFILES+= *.gcno *.gcda $(LCOV)
-.if ${MKCOVERAGE} == "yes"
-CFLAGS+=-fno-builtin -fprofile-arcs -ftest-coverage
-LDADD+= -lgcov
-CC=gcc
-.endif
+# Right now we support obtaining coverage information for system services only,
+# and for their main program code (not including their libraries) only.
+#
+# Why not userland as well: because we do not care as much, and it should be
+# possible to produce coverage information for system services without
+# recompiling the entire system with coverage support. Moreover, as of writing
+# we do not have libprofile_rt, making it impossible to compile regular
+# programs with coverage support altogether.
+#
+# Why not system service libraries (eg libsys) as well: practical concerns..
+# 1) As of writing, even for such libraries we make a regular and a PIC
+# version, both producing a .gcno file for each object. The PIC version is
+# compiled last, while the regular version is used for the library archive.
+# The result is a potential mismatch between the compile-time coverage
+# metadata and the run-time coverage counts.
+# 2) The kernel has no coverage support, and with its self-relocation it would
+# be tricky to add support for it. As a result, libraries used by the
+# kernel would have to be excluded from being compiled with coverage support
+# so as not to create problems. One could argue that that is a good thing
+# because eg libminc and libsys create too many small result units (see also
+# the current hardcoded limit in libsys/llvm_gcov.c).
+# 3) gcov-pull(8) strips paths, which results in lots of manual work to figure
+# out what file belongs to which library, even ignoring object name
+# conflicts, for example between libraries.
+# 4) In order to produce practically useful results ("how much of libsockevent
+# is covered by the combination of LWIP and UDS" etc), gcov-pull(8) would
+# have to be extended with support for merging .gcda files. The standard
+# LLVM libprofile_rt implementation supports this, but we do not.
+# All of these issues are solvable, but for now anyone interested in coverage
+# for a particular system service library will have to mess with individual
+# makefiles themselves.
+
+.if ${MKCOVERAGE:Uno} == "yes"
+.if ${ACTIVE_CC} == "gcc"
+# Leftovers for GCC. It is not clear whether these still work at all.
+COVCPPFLAGS?= -fno-builtin -fprofile-arcs -ftest-coverage
+COVLDADD?= -lgcov
+.else # ${ACTIVE_CC} != "gcc"
+# We assume LLVM/clang here. For other compilers this will likely break the
+# MKCOVERAGE compilation, which is a good indication that support for them
+# should be added here.
+COVCPPFLAGS?= --coverage -g -O0
+COVLDADD?=
+.endif # ${ACTIVE_CC} != "gcc"
+.endif # ${MKCOVERAGE:Uno} == "yes"
lcov:
lcov -c -d . >$(LCOV)
AFLAGS+= -D__ASSEMBLY__
COPTS+= -fno-builtin
+# For MKCOVERAGE builds, enable coverage options.
+.if ${MKCOVERAGE:Uno} == "yes"
+CPPFLAGS+= ${COVCPPFLAGS}
+LDADD+= ${COVLDADD}
+.endif # ${MKCOVERAGE:Uno} == "yes"
+
# LSC Static linking, order matters!
# We can't use --start-group/--end-group as they are not supported by our
# version of clang.