[RFC 07/19] ktf: Simple coverage support

Knut Omang
2019-08-13T06:11:42Z

From: Alan Maguire <alan.maguire@oracle.com>

Allows reports of code coverage and memory allocation.

ktf_cov.c: Code coverage support implementation for KTF.
ktf_cov.h: Code coverage support interface for KTF.

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
Signed-off-by: Knut Omang <knut.omang@oracle.com>
---
tools/testing/selftests/ktf/kernel/ktf_cov.c | 690 ++++++++++++++++++++-
tools/testing/selftests/ktf/kernel/ktf_cov.h | 94 +++-
2 files changed, 784 insertions(+)
create mode 100644 tools/testing/selftests/ktf/kernel/ktf_cov.c
create mode 100644 tools/testing/selftests/ktf/kernel/ktf_cov.h

diff --git a/tools/testing/selftests/ktf/kernel/ktf_cov.c b/tools/testing/selftests/ktf/kernel/ktf_cov.c
new file mode 100644
index 0000000..f917994
--- /dev/null
+++ b/tools/testing/selftests/ktf/kernel/ktf_cov.c
@@ -0,0 +1,690 @@
+/*
+ * Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Author: Alan Maguire <alan.maguire@oracle.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * ktf_cov.c: Code coverage support implementation for KTF.
+ */
+#include <linux/kallsyms.h>
+#include <linux/debugfs.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/stacktrace.h>
+#ifdef CONFIG_SLUB
+#include <linux/slub_def.h>
+#endif /* CONFIG_SLUB */
+#ifdef CONFIG_SLAB
+#include <linux/slab_def.h>
+#endif
+#include <linux/string.h>
+#include <linux/kprobes.h>
+#include <linux/ptrace.h>
+#include "ktf.h"
+#include "ktf_map.h"
+#include "ktf_cov.h"
+
+/* It may seem odd that we use a refcnt field in ktf_cov_entry structures
+ * in addition to using krefcount management via the ktf_map. The reasoning
+ * here is that if we enable and then disable coverage, we do not want to
+ * purge the entry data as we likely want to examine counts after disabling
+ * coverage. So the first enable will add entries to the cov_entry map and
+ * subsequent disable/enables will simply update the entry's refcnt. The
+ * free function below should only be called therefore from cleanup context
+ * when the cov entries are finally removed from the cov_entry map.
+ */
+static void ktf_cov_entry_free(struct ktf_map_elem *elem)
+{
+ struct ktf_cov_entry *entry = container_of(elem, struct ktf_cov_entry,
+ kmap);
+ if (entry->refcnt > 0)
+ unregister_kprobe(&entry->kprobe);
+ kfree(entry);
+}
+
+/* Comparison function is subtle. We want to be able to compare key1
+ * and key2 here, where key1 may either be an existing object, in which
+ * case it has an address and size; or it may be an object offset, in which
+ * case k1's address will be the address with offset of size 0. In both
+ * cases for the -1 case we can simply check if k1's address is less than
+ * k2's. For the 1 case, we need to ensure that the address is >=
+ * k2's address and it's size, since this ensures the address does not
+ * fall within the object bounds. Finally we are left with the case
+ * that k1.address >= k2.address _and_ it falls within the bounds of k2,
+ * which we consider a match. For a concrete example of how this matching
+ * is used, see how we walk the stack of functions within the kmalloc
+ * kretprobe below: we will have a function + offset on the stack, and we
+ * want to see if this offset falls within a function in our coverage entry
+ * map. If it does, we track the allocation. The implicit assumption is
+ * no overlap between different objects.
+ */
+static int ktf_cov_obj_compare(const char *key1, const char *key2)
+{
+ struct ktf_cov_obj_key *k1 = (struct ktf_cov_obj_key *)key1;
+ struct ktf_cov_obj_key *k2 = (struct ktf_cov_obj_key *)key2;
+
+ if (k1->address < k2->address)
+ return -1;
+ if (k1->address >= (k2->address + k2->size))
+ return 1;
+ return 0;
+}
+
+void ktf_cov_entry_get(struct ktf_cov_entry *entry)
+{
+ ktf_map_elem_get(&entry->kmap);
+}
+
+void ktf_cov_entry_put(struct ktf_cov_entry *entry)
+{
+ ktf_map_elem_put(&entry->kmap);
+}
+
+/* Global map for address-> symbol/module mapping. Sort via symbol address
+ * and size combination, see ktf_cov_obj_compare() above for comparison
+ * logic.
+ */
+static DEFINE_KTF_MAP(cov_entry_map, ktf_cov_obj_compare, ktf_cov_entry_free);
+
+struct ktf_cov_entry *ktf_cov_entry_find(unsigned long addr, unsigned long size)
+{
+ struct ktf_cov_obj_key k;
+
+ k.address = addr;
+ k.size = size;
+
+ return ktf_map_find_entry(&cov_entry_map, (char *)&k,
+ struct ktf_cov_entry, kmap);
+}
+
+static void ktf_cov_free(struct ktf_map_elem *elem)
+{
+ struct ktf_cov *cov = container_of(elem, struct ktf_cov, kmap);
+
+ kfree(cov);
+}
+
+void ktf_cov_put(struct ktf_cov *cov)
+{
+ ktf_map_elem_put(&cov->kmap);
+}
+
+/* Coverage object map. Just modules supported for now, sort by name. */
+static DEFINE_KTF_MAP(cov_map, NULL, ktf_cov_free);
+
+struct ktf_cov *ktf_cov_find(const char *module)
+{
+ return ktf_map_find_entry(&cov_map, module, struct ktf_cov, kmap);
+}
+
+/* cache for memory objects used to track allocations */
+static struct kmem_cache *cov_mem_cache;
+
+static void ktf_cov_mem_free(struct ktf_map_elem *elem)
+{
+ struct ktf_cov_mem *m = container_of(elem, struct ktf_cov_mem,
+ kmap);
+
+ kmem_cache_free(cov_mem_cache, m);
+}
+
+/* Global map for tracking memory allocations */
+DEFINE_KTF_MAP(cov_mem_map, ktf_cov_obj_compare, ktf_cov_mem_free);
+EXPORT_SYMBOL(cov_mem_map);
+
+struct ktf_cov_mem *ktf_cov_mem_find(unsigned long addr, unsigned long size)
+{
+ struct ktf_cov_obj_key k;
+
+ k.address = addr;
+ k.size = size;
+
+ return ktf_map_find_entry(&cov_mem_map, (char *)&k,
+ struct ktf_cov_mem, kmap);
+}
+
+void ktf_cov_mem_get(struct ktf_cov_mem *m)
+{
+ ktf_map_elem_get(&m->kmap);
+}
+
+void ktf_cov_mem_put(struct ktf_cov_mem *m)
+{
+ ktf_map_elem_put(&m->kmap);
+}
+
+static void ktf_cov_mem_remove(struct ktf_cov_mem *m)
+{
+ ktf_map_remove_elem(&cov_mem_map, &m->kmap);
+}
+
+/* Do not use ktf_cov_entry_find() here as we can get entry directly
+ * from probe address (as probe is first field in struct ktf_cov_entry).
+ * No reference counting issues should apply as when entry refcnt drops
+ * to 0 we unregister the kprobe prior to freeing the entry.
+ */
+static int ktf_cov_handler(struct kprobe *p, struct pt_regs *regs)
+{
+ struct ktf_cov_entry *entry = (struct ktf_cov_entry *)p;
+
+ /* Make sure probe is ours... */
+ if (!entry || entry->magic != KTF_COV_ENTRY_MAGIC)
+ return 0;
+ entry->count++;
+ if (entry->count == 1 && entry->cov)
+ entry->cov->count++;
+ return 0;
+}
+
+static int ktf_cov_init_symbol(void *data, const char *name,
+ struct module *mod, unsigned long addr)
+{
+ struct ktf_cov_entry *entry;
+ struct ktf_cov *cov = data;
+ char buf[256];
+
+ if (!mod || !cov)
+ return 0;
+
+ if (!try_module_get(mod))
+ return 0;
+
+ /* module_mutex is grabbed by register_kprobe() */
+ mutex_unlock(&module_mutex);
+
+ /* We only care about symbols for cov-specified module. */
+ if (strcmp(mod->name, cov->kmap.key))
+ goto out;
+
+ /* We don't probe ourselves and functions called within probe ctxt. */
+ if (strncmp(name, "ktf_cov", strlen("ktf_cov")) == 0 ||
+ strcmp(name, "ktf_map_find") == 0)
+ goto out;
+
+ /* Check if we're already covered for this module/symbol. */
+ entry = ktf_cov_entry_find(addr, 0);
+ if (entry) {
+ tlog(T_DEBUG, "%s already present in coverage: %s",
+ name, entry->name);
+ ktf_cov_entry_put(entry);
+ goto out;
+ }
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ (void)strlcpy(entry->name, name, sizeof(entry->name));
+ entry->magic = KTF_COV_ENTRY_MAGIC;
+ entry->cov = cov;
+ entry->refcnt = 1;
+
+ entry->kprobe.pre_handler = ktf_cov_handler;
+ entry->kprobe.symbol_name = entry->name;
+
+ /* Ugh - we try to register a kprobe as a means of determining
+ * if the symbol is a function.
+ */
+ if (register_kprobe(&entry->kprobe) < 0) {
+ /* not a probe-able function */
+ kfree(entry);
+ goto out;
+ }
+ entry->key.address = addr;
+ entry->key.size = ktf_symbol_size(addr);
+ (void)sprint_symbol(buf, entry->key.address);
+ if (ktf_map_elem_init(&entry->kmap, (char *)&entry->key) < 0 ||
+ ktf_map_insert(&cov_entry_map, &entry->kmap) < 0) {
+ unregister_kprobe(&entry->kprobe);
+ kfree(entry);
+ goto out;
+ }
+ tlog(T_DEBUG, "Added %s/%s (%p, size %lu) to coverage: %s",
+ mod->name, entry->name, (void *)entry->kprobe.addr,
+ entry->key.size, buf);
+
+ cov->total++;
+ ktf_cov_entry_put(entry);
+
+out:
+ mutex_lock(&module_mutex);
+ module_put(mod);
+ return 0;
+}
+
+static int ktf_cov_kmem_cache_alloc_handler(struct kretprobe_instance *,
+ struct pt_regs *);
+
+static unsigned long register_kretprobe_size;
+
+/* Handler tracking allocations. Determine if any functions we are
+ * tracking coverage for (coverage entries) are on the stack; if so
+ * we track the allocation.
+ */
+static int ktf_cov_kmem_alloc_entry(struct ktf_cov_mem *m, unsigned long bytes)
+{
+ struct ktf_cov_entry *entry = NULL;
+ int n;
+
+ /* We don't care about 0-length allocations. */
+ if (!bytes)
+ return 0;
+
+ /* Find first cov entry on stack to allow us to attribute traced
+ * allocation to first coverage entry we come across.
+ */
+ m->nr_entries = stack_trace_save(m->stack_entries, KTF_COV_MAX_STACK_DEPTH, 1);
+ for (n = 0; n < m->nr_entries; n++) {
+ /* avoid recursive enter when allocating cov mem */
+ if (m->stack_entries[n] ==
+ (unsigned long)ktf_cov_kmem_cache_alloc_handler)
+ break;
+ /* ignore allocs as a result of registering probes */
+ if (m->stack_entries[n] >
+ (unsigned long)register_kretprobe &&
+ m->stack_entries[n] < ((unsigned long)register_kretprobe +
+ register_kretprobe_size))
+ break;
+ entry = ktf_cov_entry_find(m->stack_entries[n], 0);
+ if (entry)
+ break;
+ }
+ if (!entry) {
+ m->nr_entries = 0;
+ return 0;
+ }
+ ktf_cov_entry_put(entry);
+
+ m->key.size = bytes;
+ /* Have to wait until alloc returns to get key.address */
+
+ return 0;
+}
+
+/* Handler tracking kmalloc allocations. */
+static int ktf_cov_kmalloc_entry_handler(struct kretprobe_instance *ri,
+ struct pt_regs *regs)
+{
+ struct ktf_cov_mem *m = (struct ktf_cov_mem *)ri->data;
+ unsigned long bytes = (unsigned long)KTF_ENTRY_PROBE_ARG0;
+
+ return ktf_cov_kmem_alloc_entry(m, bytes);
+}
+
+static int ktf_cov_kmem_cache_alloc_entry_handler(struct kretprobe_instance *ri,
+ struct pt_regs *regs)
+{
+ struct kmem_cache *cache =
+ (struct kmem_cache *)KTF_ENTRY_PROBE_ARG0;
+ struct ktf_cov_mem *m = (struct ktf_cov_mem *)ri->data;
+ unsigned long bytes;
+
+ if (!cache)
+ return 0;
+
+ bytes = cache->object_size;
+ if (cache == cov_mem_cache)
+ return 0;
+ return ktf_cov_kmem_alloc_entry(m, bytes);
+}
+
+static int ktf_cov_kmem_alloc_return(struct ktf_cov_mem *m,
+ unsigned long ret)
+{
+ struct ktf_cov_mem *mm;
+
+ m->key.address = ret;
+ mm = kmem_cache_alloc(cov_mem_cache, GFP_NOWAIT);
+ if (!mm)
+ return 0;
+ memcpy(mm, m, sizeof(*mm));
+ if (ktf_map_elem_init(&mm->kmap, (char *)&mm->key) < 0 ||
+ ktf_map_insert(&cov_mem_map, &mm->kmap) < 0) {
+ /* This can happen as inexplicably the same probe
+ * can fire twice for _kmalloc; this results in
+ * us attempting to add the same address twice, with
+ * the result that we get -EEXIST from ktf_map_insert()
+ * the second time. Annoying but the end result is
+ * we track the allocation once, which is what we want.
+ */
+ terr("Failed to insert cov_mem %p", (void *)ret);
+ kmem_cache_free(cov_mem_cache, mm);
+ }
+ tlog(T_DEBUG, "cov_mem: tracking allocation %p", (void *)m->key.address);
+ m->nr_entries = 0;
+ return 0;
+}
+
+static int ktf_cov_kmalloc_handler(struct kretprobe_instance *ri,
+ struct pt_regs *regs)
+{
+ struct ktf_cov_mem *m = (struct ktf_cov_mem *)ri->data;
+ unsigned long ret = regs_return_value(regs);
+
+ if (m->nr_entries)
+ return ktf_cov_kmem_alloc_return(m, ret);
+ return 0;
+}
+
+static int ktf_cov_kmem_cache_alloc_handler(struct kretprobe_instance *ri,
+ struct pt_regs *regs)
+{
+ struct kmem_cache *cache =
+ (struct kmem_cache *)KTF_ENTRY_PROBE_ARG0;
+ struct ktf_cov_mem *m = (struct ktf_cov_mem *)ri->data;
+ unsigned long ret = regs_return_value(regs);
+
+ if (cache == cov_mem_cache)
+ return 0;
+
+ if (m->nr_entries)
+ return ktf_cov_kmem_alloc_return(m, ret);
+ return 0;
+}
+
+static int ktf_cov_kmem_free_entry(unsigned long tofree)
+{
+ struct ktf_cov_mem *m;
+
+ if (!tofree)
+ return 0;
+
+ m = ktf_cov_mem_find(tofree, 0);
+ if (m) {
+ tlog(T_DEBUG, "cov_mem: freeing allocation %p",
+ (void *)m->key.address);
+ ktf_cov_mem_remove(m);
+ ktf_cov_mem_put(m);
+ }
+ return 0;
+}
+
+static int ktf_cov_kfree_entry_handler(struct kretprobe_instance *ri,
+ struct pt_regs *regs)
+{
+ unsigned long tofree = (unsigned long)KTF_ENTRY_PROBE_ARG0;
+
+ if (!tofree)
+ return 0;
+
+ return ktf_cov_kmem_free_entry(tofree);
+}
+
+static int ktf_cov_kmem_cache_free_entry_handler(struct kretprobe_instance *ri,
+ struct pt_regs *regs)
+{
+ struct kmem_cache *cache =
+ (struct kmem_cache *)KTF_ENTRY_PROBE_ARG0;
+ unsigned long tofree = (unsigned long)KTF_ENTRY_PROBE_ARG1;
+
+ if (!tofree || cache == cov_mem_cache)
+ return 0;
+
+ return ktf_cov_kmem_free_entry(tofree);
+}
+
+static struct kretprobe cov_mem_probes[] = {
+ { .kp = { .symbol_name = "__kmalloc" },
+ .handler = ktf_cov_kmalloc_handler,
+ .entry_handler = ktf_cov_kmalloc_entry_handler,
+ .data_size = sizeof(struct ktf_cov_mem),
+ .maxactive = 0, /* assumes default value */
+ },
+ { .kp = { .symbol_name = "kmem_cache_alloc" },
+ .handler = ktf_cov_kmem_cache_alloc_handler,
+ .entry_handler = ktf_cov_kmem_cache_alloc_entry_handler,
+ .data_size = sizeof(struct ktf_cov_mem),
+ .maxactive = 0, /* assumes default value */
+ },
+ { .kp = { .symbol_name = "kfree" },
+ .handler = NULL,
+ .entry_handler = ktf_cov_kfree_entry_handler,
+ .data_size = 0,
+ .maxactive = 0, /* assumes default value */
+ },
+ { .kp = { .symbol_name = "kmem_cache_free" },
+ .handler = NULL,
+ .entry_handler = ktf_cov_kmem_cache_free_entry_handler,
+ .data_size = 0,
+ .maxactive = 0, /* assumes default value */
+ }
+};
+
+static int cov_opt_mem_cnt;
+
+static int ktf_cov_init_opts(struct ktf_cov *cov)
+{
+ int i, ret = 0;
+
+ if (cov->opts & KTF_COV_OPT_MEM && ++cov_opt_mem_cnt == 1) {
+ if (!cov_mem_cache) {
+ cov_mem_cache =
+ kmem_cache_create("ktf_cov_mem_cache",
+ sizeof(struct ktf_cov_mem), 0,
+ SLAB_HWCACHE_ALIGN | SLAB_PANIC,
+ NULL);
+
+ if (!cov_mem_cache)
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cov_mem_probes); i++) {
+ /* reset in case we're re-registering */
+ cov_mem_probes[i].kp.addr = NULL;
+ cov_mem_probes[i].kp.flags = 0;
+ ret = register_kretprobe(&cov_mem_probes[i]);
+ if (ret) {
+ tlog(T_DEBUG,
+ "%d: failed to register retprobe for %s",
+ ret, cov_mem_probes[i].kp.symbol_name);
+ return ret;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static void ktf_cov_cleanup_opts(struct ktf_cov *cov)
+{
+ int i;
+
+ if (cov->opts & KTF_COV_OPT_MEM && --cov_opt_mem_cnt == 0) {
+ for (i = 0; i < ARRAY_SIZE(cov_mem_probes); i++) {
+ if (cov_mem_probes[i].nmissed > 0) {
+ tlog(T_INFO, "%s: retprobe missed %d.",
+ cov_mem_probes[i].kp.symbol_name,
+ cov_mem_probes[i].nmissed);
+ }
+ if (cov_mem_probes[i].kp.addr)
+ unregister_kretprobe(&cov_mem_probes[i]);
+ }
+ }
+}
+
+/* If the module we are monitoring coverage for was unloaded/reloaded
+ * while coverage was disabled, we can end up re-enabling kprobes at
+ * different addresses for the same function. The problem is however
+ * we reference coverage entries by their address in the coverage
+ * entry map, so we need to clean it up to reflect the new locations
+ * of the probes. So we remove/re-add the entries with the updated
+ * addresses. It would obviously be easier to just remove the entries
+ * on coverage disable, but that limits our ability to examine coverage
+ * data - a common pattern is enable coverage, run test(s), disable
+ * coverage, check coverage data.
+ */
+static void ktf_cov_update_entries(const char *name, struct ktf_cov *cov)
+{
+ struct ktf_cov_entry *entry = ktf_map_first_entry(&cov_entry_map,
+ struct ktf_cov_entry,
+ kmap);
+
+ while (entry) {
+ if (entry->cov != cov ||
+ (unsigned long)entry->kprobe.addr == entry->key.address) {
+ entry = ktf_map_next_entry(entry, kmap);
+ continue;
+ }
+
+ /* Address has changed; remove entry with old address as key
+ * and re-add with new address/size as key (size may have
+ * changed if module was re-compiled).
+ */
+ ktf_map_remove_elem(&cov_entry_map, &entry->kmap);
+ entry->key.address = (unsigned long)entry->kprobe.addr;
+ entry->key.size = ktf_symbol_size(entry->key.address);
+ if (ktf_map_elem_init(&entry->kmap, (char *)&entry->key) < 0 ||
+ ktf_map_insert(&cov_entry_map, &entry->kmap) < 0) {
+ tlog(T_DEBUG, "Failed to add %s/%s", name, entry->name);
+ unregister_kprobe(&entry->kprobe);
+ entry->refcnt--;
+ entry = ktf_map_next_entry(entry, kmap);
+ } else {
+ tlog(T_DEBUG, "Added %s/%s (%p, size %lu) to coverage",
+ name, entry->name, (void *)entry->key.address,
+ entry->key.size);
+ /* Map has changed, reset to root. */
+ entry = ktf_map_first_entry(&cov_entry_map,
+ struct ktf_cov_entry, kmap);
+ }
+ }
+}
+
+int ktf_cov_enable(const char *name, unsigned int opts)
+{
+ struct ktf_cov *cov = ktf_cov_find(name);
+ struct ktf_cov_entry *entry;
+ int ret = 0;
+
+#ifndef KTF_PROBE_SUPPORT
+ return -ENOTSUPP;
+#endif
+ if (!cov) {
+ cov = kzalloc(sizeof(*cov), GFP_KERNEL);
+ if (!cov)
+ return -ENOMEM;
+
+ cov->type = KTF_COV_TYPE_MODULE;
+ cov->opts = opts;
+ if (ktf_map_elem_init(&cov->kmap, name) < 0 ||
+ ktf_map_insert(&cov_map, &cov->kmap) < 0) {
+ tlog(T_DEBUG, "cov %s already present", cov->kmap.key);
+ kfree(cov);
+ return -EEXIST;
+ }
+ register_kretprobe_size =
+ ktf_symbol_size((unsigned long)register_kretprobe);
+ mutex_lock(&module_mutex);
+ kallsyms_on_each_symbol(ktf_cov_init_symbol, cov);
+ mutex_unlock(&module_mutex);
+ } else {
+ ktf_map_for_each_entry(entry, &cov_entry_map, kmap) {
+ if (entry->cov != cov)
+ continue;
+ if (++entry->refcnt == 1) {
+ /* reset kprobe as we're re-registering */
+ memset(&entry->kprobe, 0,
+ sizeof(entry->kprobe));
+ entry->kprobe.pre_handler = ktf_cov_handler;
+ entry->kprobe.symbol_name = entry->name;
+ ret = register_kprobe(&entry->kprobe);
+ if (ret) {
+ tlog(T_DEBUG, "Failed to add %s/%s",
+ name, entry->name);
+ entry->refcnt--;
+ }
+ }
+ }
+ /* Probe addresses/function sizes for functions may have
+ * changed if module was unloaded/reloaded - entry map
+ * needs to be updated to use new address/size as key.
+ */
+ ktf_cov_update_entries(name, cov);
+ }
+
+ ret = ktf_cov_init_opts(cov);
+
+ ktf_cov_put(cov);
+
+ return ret;
+}
+
+void ktf_cov_disable(const char *module)
+{
+ struct ktf_cov *cov = ktf_cov_find(module);
+ struct ktf_cov_entry *entry;
+
+#ifndef KTF_PROBE_SUPPORT
+ return;
+#endif
+
+ if (!cov)
+ return;
+
+ ktf_map_for_each_entry(entry, &cov_entry_map, kmap) {
+ if (entry->cov == cov) {
+ if (--entry->refcnt == 0) {
+ unregister_kprobe(&entry->kprobe);
+ tlog(T_DEBUG, "Removed coverage %s/%s",
+ cov->kmap.key, entry->name);
+ }
+ }
+ }
+ ktf_cov_cleanup_opts(cov);
+ ktf_cov_put(cov);
+}
+
+static void ktf_cov_mem_seq_print(struct seq_file *seq)
+{
+ struct ktf_cov_mem *m;
+ char buf[256];
+ int n;
+
+ seq_puts(seq, "\nMemory in use allocated by covered functions:\n\n");
+ seq_printf(seq, "%44s %16s %10s\n", "ALLOCATION STACK", "ADDRESS",
+ "SIZE");
+ ktf_for_each_cov_mem(m) {
+ for (n = 0; n < m->nr_entries; n++) {
+ sprint_symbol(buf, m->stack_entries[n]);
+ seq_printf(seq, "%44s", buf);
+ if (n == 0)
+ seq_printf(seq, " %16p %10lu",
+ (void *)m->key.address,
+ m->key.size);
+ seq_puts(seq, "\n");
+ }
+ seq_puts(seq, "\n");
+ }
+}
+
+void ktf_cov_seq_print(struct seq_file *seq)
+{
+ struct ktf_cov_entry *entry;
+ struct ktf_cov *cov;
+
+ seq_printf(seq, "%10s %44s %10s\n", "MODULE", "#FUNCTIONS",
+ "#CALLED");
+ ktf_map_for_each_entry(cov, &cov_map, kmap)
+ seq_printf(seq, "%10s %44d %10d\n",
+ cov->kmap.key, cov->total, cov->count);
+
+ seq_printf(seq, "\n%10s %44s %10s\n", "MODULE", "FUNCTION", "COUNT");
+ ktf_map_for_each_entry(entry, &cov_entry_map, kmap)
+ seq_printf(seq, "%10s %44s %10d\n",
+ entry->cov ? entry->cov->kmap.key : "-",
+ entry->name, entry->count);
+
+ ktf_cov_mem_seq_print(seq);
+}
+
+
+void ktf_cov_cleanup(void)
+{
+ struct ktf_cov *cov;
+ char name[KTF_MAX_KEY];
+
+ ktf_map_for_each_entry(cov, &cov_map, kmap) {
+ ktf_cov_disable(ktf_map_elem_name(&cov->kmap, name));
+ }
+ ktf_map_delete_all(&cov_map);
+ ktf_map_delete_all(&cov_entry_map);
+ ktf_map_delete_all(&cov_mem_map);
+ kmem_cache_destroy(cov_mem_cache);
+}
diff --git a/tools/testing/selftests/ktf/kernel/ktf_cov.h b/tools/testing/selftests/ktf/kernel/ktf_cov.h
new file mode 100644
index 0000000..56ae753
--- /dev/null
+++ b/tools/testing/selftests/ktf/kernel/ktf_cov.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Author: Alan Maguire <alan.maguire@oracle.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * ktf_cov.h: Code coverage support interface for KTF.
+ */
+
+#ifndef KTF_COV_H
+#define KTF_COV_H
+
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/kprobes.h>
+#include "ktf.h"
+#include "ktf_map.h"
+
+enum ktf_cov_type {
+ KTF_COV_TYPE_MODULE,
+ KTF_COV_TYPE_MAX,
+};
+
+struct ktf_cov {
+ struct ktf_map_elem kmap;
+ enum ktf_cov_type type; /* only modules supported for now. */
+ int count; /* number of unique functions called */
+ int total; /* total number of functions */
+ unsigned int opts;
+};
+
+/* Key for coverage entries (functions) consists in function address _and_
+ * size - this allows us to find offsets in function on stack. Also used
+ * to track allocated memory - allocated address + size.
+ */
+struct ktf_cov_obj_key {
+ unsigned long address;
+ unsigned long size;
+};
+
+#define KTF_COV_ENTRY_MAGIC 0xc07e8a5e
+struct ktf_cov_entry {
+ struct kprobe kprobe;
+ int magic; /* magic number identifying entry */
+ struct ktf_map_elem kmap;
+ char name[KTF_MAX_KEY];
+ struct ktf_cov_obj_key key;
+ struct ktf_cov *cov;
+ struct ktf_map cov_mem;
+ int refcnt;
+ int count;
+};
+
+#define KTF_COV_MAX_STACK_DEPTH 32
+
+struct ktf_cov_mem {
+ struct ktf_map_elem kmap;
+ struct ktf_cov_obj_key key;
+ unsigned long flags;
+ unsigned int nr_entries;
+ unsigned long stack_entries[KTF_COV_MAX_STACK_DEPTH];
+};
+
+#define KTF_COV_MEM_IGNORE 0x1 /* avoid recursive enter */
+
+struct ktf_cov_mem_probe {
+ const char *name;
+ struct kretprobe kretprobe;
+};
+
+extern struct ktf_map cov_mem_map;
+
+#define ktf_for_each_cov_mem(pos) \
+ ktf_map_for_each_entry(pos, &cov_mem_map, kmap)
+
+struct ktf_cov_entry *ktf_cov_entry_find(unsigned long, unsigned long);
+void ktf_cov_entry_put(struct ktf_cov_entry *);
+void ktf_cov_entry_get(struct ktf_cov_entry *);
+
+struct ktf_cov *ktf_cov_find(const char *);
+void ktf_cov_put(struct ktf_cov *);
+void ktf_cov_get(struct ktf_cov *);
+
+struct ktf_cov_mem *ktf_cov_mem_find(unsigned long, unsigned long);
+void ktf_cov_mem_put(struct ktf_cov_mem *);
+void ktf_cov_mem_get(struct ktf_cov_mem *);
+
+void ktf_cov_seq_print(struct seq_file *);
+void ktf_cov_cleanup(void);
+
+int ktf_cov_enable(const char *, unsigned int);
+void ktf_cov_disable(const char *);
+
+#endif
--
git-series 0.9.1