All of lore.kernel.org
 help / color / mirror / Atom feed
From: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
To: linux-kernel@vger.kernel.org
Cc: ltp-list@lists.sourceforge.net,
	ltp-coverage@lists.sourceforge.net,
	Andrew Morton <akpm@linux-foundation.org>
Subject: [RFC PATCH 5/6] gcov: add gcov profiling infrastructure
Date: Mon, 05 May 2008 17:24:46 +0200	[thread overview]
Message-ID: <481F26BE.2060706@de.ibm.com> (raw)

From: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>

gcov profiling infrastructure:

* change kbuild to include profiling flags
* provide functions needed by profiling code
* present profiling data as files in debugfs

Signed-off-by: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
---
 Makefile              |   10 
 kernel/Makefile       |    1 
 kernel/gcov/Kconfig   |   45 +++
 kernel/gcov/Makefile  |   11 
 kernel/gcov/base.c    |  157 +++++++++++
 kernel/gcov/fs.c      |  542 ++++++++++++++++++++++++++++++++++++++++
 kernel/gcov/gcc_3_4.c |  374 +++++++++++++++++++++++++++
 kernel/gcov/gcov.h    |  101 +++++++
 lib/Kconfig.debug     |    1 
 scripts/Makefile.lib  |   10 
 10 files changed, 1247 insertions(+), 5 deletions(-)

Index: linux-2.6.26-rc1/Makefile
===================================================================
--- linux-2.6.26-rc1.orig/Makefile
+++ linux-2.6.26-rc1/Makefile
@@ -320,6 +320,7 @@ AFLAGS_MODULE   = $(MODFLAGS)
 LDFLAGS_MODULE  =
 CFLAGS_KERNEL	=
 AFLAGS_KERNEL	=
+CFLAGS_GCOV	= -fprofile-arcs -ftest-coverage
 
 
 # Use LINUXINCLUDE when you must reference the include/ directory.
@@ -345,7 +346,7 @@ export CPP AR NM STRIP OBJCOPY OBJDUMP M
 export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS
 
 export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS
-export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE
+export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV
 export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE
 
 # When compiling out-of-tree modules, put MODVERDIR in the module
@@ -1132,7 +1133,8 @@ clean: archclean $(clean-dirs)
 	@find . $(RCS_FIND_IGNORE) \
 		\( -name '*.[oas]' -o -name '*.ko' -o -name '.*.cmd' \
 		-o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' \
-		-o -name '*.symtypes' -o -name 'modules.order' \) \
+		-o -name '*.symtypes' -o -name 'modules.order' \
+		-o -name '*.gcno' \) \
 		-type f -print | xargs rm -f
 
 # mrproper - Delete all generated files, including .config
@@ -1336,8 +1338,8 @@ clean: $(clean-dirs)
 	$(call cmd,rmfiles)
 	@find $(KBUILD_EXTMOD) $(RCS_FIND_IGNORE) \
 		\( -name '*.[oas]' -o -name '*.ko' -o -name '.*.cmd' \
-		-o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' \) \
-		-type f -print | xargs rm -f
+		-o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' \
+		-o -name '*.gcno' \) -type f -print | xargs rm -f
 
 help:
 	@echo  '  Building external modules.'
Index: linux-2.6.26-rc1/scripts/Makefile.lib
===================================================================
--- linux-2.6.26-rc1.orig/scripts/Makefile.lib
+++ linux-2.6.26-rc1/scripts/Makefile.lib
@@ -100,10 +100,18 @@ _c_flags       = $(KBUILD_CFLAGS) $(ccfl
 _a_flags       = $(KBUILD_AFLAGS) $(asflags-y) $(AFLAGS_$(basetarget).o)
 _cpp_flags     = $(KBUILD_CPPFLAGS) $(cppflags-y) $(CPPFLAGS_$(@F))
 
+# Enable profiling flags for a file, directory or for all files depending on
+# variables GCOV_obj.o, GCOV and CONFIG_GCOV_PROFILE_ALL (in this order)
+ifeq ($(CONFIG_GCOV_PROFILE),y)
+_c_flags += $(if $(patsubst n%,, \
+		$(GCOV_$(basetarget).o)$(GCOV)$(CONFIG_GCOV_PROFILE_ALL)), \
+		$(CFLAGS_GCOV))
+endif
+
 # If building the kernel in a separate objtree expand all occurrences
 # of -Idir to -I$(srctree)/dir except for absolute paths (starting with '/').
 
-ifeq ($(KBUILD_SRC),)
+ifeq ($(KBUILD_SRC)$(CONFIG_GCOV_PROFILE),)
 __c_flags	= $(_c_flags)
 __a_flags	= $(_a_flags)
 __cpp_flags     = $(_cpp_flags)
Index: linux-2.6.26-rc1/kernel/gcov/Kconfig
===================================================================
--- /dev/null
+++ linux-2.6.26-rc1/kernel/gcov/Kconfig
@@ -0,0 +1,45 @@
+menu "GCOV profiling"
+
+config GCOV_PROFILE
+	bool "Activate gcov-based profiling"
+	depends on DEBUG_FS
+	---help---
+	This option enables gcov-based code profiling (e.g. for code coverage
+	measurements).
+
+	If unsure, say N.
+
+	Additionally specify CONFIG_GCOV_PROFILE_ALL=y to get profiling data
+	for the entire kernel. To enable profiling for specific files or
+	directories, add a line similar to the following to the respective
+	Makefile:
+
+	For a single file (e.g. main.o):
+	        GCOV_main.o := y
+
+	For all files in one directory:
+	        GCOV := y
+
+	To exclude files from being profiled even when CONFIG_GCOV_PROFILE_ALL
+	is specified, use:
+
+	        GCOV_main.o := n
+	and:
+	        GCOV := n
+
+	Note that the debugfs filesystem has to be mounted to access
+	profiling data.
+
+config GCOV_PROFILE_ALL
+	bool "Profile entire Kernel"
+	depends on GCOV_PROFILE
+	---help---
+	This options activates profiling for the entire kernel.
+
+	If unsure, say Y.
+
+	Note that a kernel compiled with profiling flags will be larger and
+	slower. Also be sure to exclude files from profiling which are not
+	linked to the kernel image to prevent linker errors.
+
+endmenu
Index: linux-2.6.26-rc1/kernel/gcov/base.c
===================================================================
--- /dev/null
+++ linux-2.6.26-rc1/kernel/gcov/base.c
@@ -0,0 +1,157 @@
+/*
+ *  Base functions for profiling.
+ *
+ *    Copyright IBM Corp. 2008
+ *    Author(s): Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
+ *
+ *    Uses gcc-internal data definitions.
+ *    Based on the gcov-kernel patch by:
+ *		 Hubertus Franke <frankeh@us.ibm.com>
+ *		 Nigel Hinds <nhinds@us.ibm.com>
+ *		 Rajan Ravindran <rajancr@us.ibm.com>
+ *		 Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
+ *		 Paul Larson
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/syscalls.h>
+#include "gcov.h"
+
+static gcov_callback_t gcov_callback;
+static struct gcov_info *gcov_info_head;
+static DEFINE_MUTEX(gcov_lock);
+
+static void gcov_register_info(struct gcov_info *info)
+{
+	info->next = gcov_info_head;
+	gcov_info_head = info;
+	if (gcov_callback)
+		gcov_callback(gcov_add, info);
+}
+
+#if GCC_VERSION_LOWER(3, 4)
+
+#error "GCOV profiling support for gcc versions below 3.4 not included"
+
+#else
+/* Functions needed by profiling code for gcc 3.4 and above. */
+
+unsigned int gcov_version;
+
+void __gcov_init(struct gcov_info *info)
+{
+	mutex_lock(&gcov_lock);
+	/* Check for compatible gcc version. */
+	if (gcov_version == 0) {
+		gcov_version = info->version;
+		printk(KERN_INFO TAG "gcc version %x\n", gcov_version);
+	} else if (info->version != gcov_version) {
+		printk(KERN_WARNING TAG "version mismatch in %s\n",
+		       info->filename);
+		goto out;
+	}
+	gcov_register_info(info);
+out:
+	mutex_unlock(&gcov_lock);
+}
+EXPORT_SYMBOL(__gcov_init);
+
+/* These functions may be referenced by profiling code but serve no function
+ * for kernel profiling. */
+void __gcov_flush(void)
+{
+	/* Unused. */
+}
+EXPORT_SYMBOL(__gcov_flush);
+
+void __gcov_merge_add(gcov_type *counters, unsigned int n_counters)
+{
+	/* Unused. */
+}
+EXPORT_SYMBOL(__gcov_merge_add);
+
+void __gcov_merge_single(gcov_type *counters, unsigned int n_counters)
+{
+	/* Unused. */
+}
+EXPORT_SYMBOL(__gcov_merge_single);
+
+void __gcov_merge_delta(gcov_type *counters, unsigned int n_counters)
+{
+	/* Unused. */
+}
+EXPORT_SYMBOL(__gcov_merge_delta);
+
+int __gcov_execve(const char *path, char *const argv[], char *const envp[])
+{
+	return kernel_execve(path, argv, envp);
+}
+EXPORT_SYMBOL(__gcov_execve);
+
+#endif /* GCC_VERSION */
+
+int gcov_register_callback(gcov_callback_t callback)
+{
+	struct gcov_info *info;
+	int rc;
+
+	mutex_lock(&gcov_lock);
+	if (gcov_callback) {
+		rc = -EBUSY;
+		goto out;
+	}
+	rc = 0;
+	gcov_callback = callback;
+	/* Perform callback for previously registered entries. */
+	for (info = gcov_info_head; info; info = info->next)
+		gcov_callback(gcov_add, info);
+out:
+	mutex_unlock(&gcov_lock);
+
+	return rc;
+}
+
+static inline int within(void *addr, void *start, unsigned long size)
+{
+        return (addr >= start && (void *) addr < start + size);
+}
+
+static int gcov_module_notifier(struct notifier_block *nb, unsigned long event,
+				void *data)
+{
+	struct module *mod = data;
+	struct gcov_info *info;
+	struct gcov_info *prev;
+
+	if (event != MODULE_STATE_GOING)
+		return NOTIFY_OK;
+	mutex_lock(&gcov_lock);
+	prev = NULL;
+	/* Remove entries located in module from linked list. */
+	for (info = gcov_info_head; info; info = info->next) {
+		if (within(info, mod->module_core, mod->core_size)) {
+			if (prev)
+				prev->next = info->next;
+			else
+				gcov_info_head = info->next;
+			if (gcov_callback)
+				gcov_callback(gcov_remove, info);
+		} else
+			prev = info;
+	}
+	mutex_unlock(&gcov_lock);
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block gcov_nb = {
+	.notifier_call	= gcov_module_notifier,
+};
+
+static int __init gcov_init(void)
+{
+	return register_module_notifier(&gcov_nb);
+}
+__initcall(gcov_init);
Index: linux-2.6.26-rc1/kernel/gcov/gcov.h
===================================================================
--- /dev/null
+++ linux-2.6.26-rc1/kernel/gcov/gcov.h
@@ -0,0 +1,101 @@
+/*
+ *  Profiling infrastructure declarations.
+ *
+ *    Copyright IBM Corp. 2008
+ *    Author(s): Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
+ *
+ *    Uses gcc-internal data definitions.
+ */
+
+#ifndef GCOV_H
+#define GCOV_H GCOV_H
+
+#include <linux/types.h>
+
+#define TAG	"gcov: "
+#define GCC_VERSION_LOWER(major, minor) ((__GNUC__ < major) || \
+					 (__GNUC__ == major) && \
+					 (__GNUC_MINOR__ < minor))
+#if GCC_VERSION_LOWER(3, 4)
+
+#error "GCOV profiling support for gcc versions below 3.4 not included"
+
+#else
+/* Profiling data types used for gcc 3.4 and above. */
+
+#define GCOV_COUNTERS		5
+#define GCOV_DATA_MAGIC		((unsigned int) 0x67636461)
+#define GCOV_TAG_FUNCTION	((unsigned int) 0x01000000)
+#define GCOV_TAG_COUNTER_BASE	((unsigned int) 0x01a10000)
+#define GCOV_TAG_FOR_COUNTER(count)					\
+	(GCOV_TAG_COUNTER_BASE + ((unsigned int) (count) << 17))
+
+#if BITS_PER_LONG >= 64
+typedef long gcov_type;
+#else
+typedef long long gcov_type;
+#endif
+
+struct gcov_fn_info {
+	unsigned int ident;
+	unsigned int checksum;
+	unsigned int n_ctrs[0];
+};
+
+struct gcov_ctr_info {
+	unsigned int num;
+	gcov_type *values;
+	void (*merge)(gcov_type *, unsigned int);
+};
+
+struct gcov_info {
+	unsigned int version;
+	struct gcov_info *next;
+	unsigned int stamp;
+	const char *filename;
+	unsigned int n_functions;
+	const struct gcov_fn_info *functions;
+	unsigned int ctr_mask;
+	struct gcov_ctr_info counts[0];
+};
+
+extern unsigned int gcov_version;
+
+#endif /* GCC_VERSION_LOWER */
+
+/* Base interface. */
+enum gcov_action {
+	gcov_add,
+	gcov_remove,
+};
+
+typedef void (*gcov_callback_t)(enum gcov_action, struct gcov_info *);
+
+int gcov_register_callback(gcov_callback_t);
+
+/* Iterator control. */
+struct seq_file;
+
+void *gcov_iter_new(struct gcov_info *);
+void gcov_iter_start(void *);
+int gcov_iter_next(void *);
+int gcov_iter_write(void *, struct seq_file *);
+struct gcov_info *gcov_iter_get_info(void *);
+
+/* gcov_info control. */
+void gcov_info_reset(struct gcov_info *);
+int gcov_info_is_compatible(struct gcov_info *, struct gcov_info *);
+void gcov_info_add(struct gcov_info *, struct gcov_info *);
+struct gcov_info *gcov_info_dup(struct gcov_info *);
+void gcov_info_free(struct gcov_info *);
+
+struct gcov_link_t {
+	enum {
+		obj_tree,
+		src_tree,
+	} dir;
+	const char *ext;
+};
+extern const struct gcov_link_t gcov_link[];
+
+#endif /* GCOV_H */
Index: linux-2.6.26-rc1/lib/Kconfig.debug
===================================================================
--- linux-2.6.26-rc1.orig/lib/Kconfig.debug
+++ linux-2.6.26-rc1/lib/Kconfig.debug
@@ -675,5 +675,6 @@ config FIREWIRE_OHCI_REMOTE_DMA
 	  If unsure, say N.
 
 source "samples/Kconfig"
+source "kernel/gcov/Kconfig"
 
 source "lib/Kconfig.kgdb"
Index: linux-2.6.26-rc1/kernel/Makefile
===================================================================
--- linux-2.6.26-rc1.orig/kernel/Makefile
+++ linux-2.6.26-rc1/kernel/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_TASK_DELAY_ACCT) += delayac
 obj-$(CONFIG_TASKSTATS) += taskstats.o tsacct.o
 obj-$(CONFIG_MARKERS) += marker.o
 obj-$(CONFIG_LATENCYTOP) += latencytop.o
+obj-$(CONFIG_GCOV_PROFILE) += gcov/
 
 ifneq ($(CONFIG_SCHED_NO_NO_OMIT_FRAME_POINTER),y)
 # According to Alan Modra <alan@linuxcare.com.au>, the -fno-omit-frame-pointer is
Index: linux-2.6.26-rc1/kernel/gcov/Makefile
===================================================================
--- /dev/null
+++ linux-2.6.26-rc1/kernel/gcov/Makefile
@@ -0,0 +1,11 @@
+EXTRA_CFLAGS := -DSRCTREE='"$(srctree)"' -DOBJTREE='"$(objtree)"'
+
+gcc-ver-lower-3.4 := $(call cc-ifversion, -lt, 0304, y)
+
+obj-$(CONFIG_GCOV_PROFILE) := base.o fs.o
+
+ifeq ($(gcc-ver-lower-3.4),y)
+$(error GCOV profiling support for gcc versions below 3.4 not included)
+else
+obj-$(CONFIG_GCOV_PROFILE) += gcc_3_4.o
+endif
Index: linux-2.6.26-rc1/kernel/gcov/fs.c
===================================================================
--- /dev/null
+++ linux-2.6.26-rc1/kernel/gcov/fs.c
@@ -0,0 +1,542 @@
+/*
+ *  Filesystem for exporting profiling data to userspace.
+ *
+ *    Copyright IBM Corp. 2008
+ *    Author(s): Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
+ *
+ *    Uses gcc-internal data definitions.
+ *    Based on the gcov-kernel patch by:
+ *		 Hubertus Franke <frankeh@us.ibm.com>
+ *		 Nigel Hinds <nhinds@us.ibm.com>
+ *		 Rajan Ravindran <rajancr@us.ibm.com>
+ *		 Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
+ *		 Paul Larson
+ *		 Yi CDL Yang
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/seq_file.h>
+#include "gcov.h"
+
+struct node_t {
+	struct list_head list;
+	struct list_head children;
+	struct list_head all;
+	struct node_t *parent;
+	struct gcov_info *info;
+	struct gcov_info *ghost;
+	struct dentry *dentry;
+	struct dentry **links;
+	char name[0];
+};
+
+static const char objtree[] = OBJTREE;
+static const char srctree[] = SRCTREE;
+static struct node_t root_node;
+static struct list_head all_head;
+static struct dentry *reset_dentry;
+static DEFINE_MUTEX(node_lock);
+
+/* If non-zero, keep copies of profiling data for unloaded modules. */
+static int gcov_persist = 1;
+
+static int __init gcov_persist_setup(char *str)
+{
+	int val;
+	char delim;
+
+	if (sscanf(str, "%d %c", &val, &delim) != 1) {
+		printk(KERN_WARNING TAG "invalid gcov_persist parameter '%s'\n",
+		       str);
+		return 0;
+	}
+	printk(KERN_INFO TAG "setting gcov_persist to %d\n", val);
+	gcov_persist = val;
+
+	return 1;
+}
+__setup("gcov_persist=", gcov_persist_setup);
+
+static void *gcov_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	loff_t i;
+
+	gcov_iter_start(seq->private);
+	for (i = 0; i < *pos; i++)
+		if (gcov_iter_next(seq->private))
+			return NULL;
+
+	return seq->private;
+}
+
+static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos)
+{
+	struct iterator_t *iter = data;
+
+	if (gcov_iter_next(iter))
+		return NULL;
+	(*pos)++;
+
+	return iter;
+}
+
+static int gcov_seq_show(struct seq_file *seq, void *data)
+{
+	struct iterator_t *iter = data;
+
+	return gcov_iter_write(iter, seq);
+}
+
+static void gcov_seq_stop(struct seq_file *seq, void *data)
+{
+	/* Unused. */
+}
+
+static struct seq_operations seq_ops = {
+	.start = gcov_seq_start,
+	.next = gcov_seq_next,
+	.show = gcov_seq_show,
+	.stop = gcov_seq_stop,
+};
+
+struct gcov_info *get_node_info(struct node_t *node)
+{
+	if (node->info)
+		return node->info;
+
+	return node->ghost;
+}
+
+static int gcov_seq_open(struct inode *inode, struct file *file)
+{
+	struct node_t *node = inode->i_private;
+	struct iterator_t *iter;
+	struct seq_file *seq;
+	struct gcov_info *info;
+	int rc;
+
+	mutex_lock(&node_lock);
+	/* Read from a profiling data copy to minimize reference tracking
+	 * complexity and concurrent access. */
+	info = gcov_info_dup(get_node_info(node));
+	if (!info) {
+		rc = -ENOMEM;
+		goto out_unlock;
+	}
+	iter = gcov_iter_new(info);
+	if (!iter) {
+		rc = -ENOMEM;
+		goto out_free;
+	}
+	rc = seq_open(file, &seq_ops);
+	if (rc)
+		goto out_free2;
+	seq = file->private_data;
+	seq->private = iter;
+	goto out_unlock;
+
+out_free2:
+	kfree(iter);
+out_free:
+	kfree(info);
+out_unlock:
+	mutex_unlock(&node_lock);
+
+	return rc;
+}
+
+static int gcov_seq_release(struct inode *inode, struct file *file)
+{
+	struct gcov_info *info;
+	struct seq_file *seq = file->private_data;
+
+	info = gcov_iter_get_info(seq->private);
+	kfree(info);
+	kfree(seq->private);
+	seq_release(inode, file);
+
+	return 0;
+}
+
+static struct node_t *get_node_by_name(const char *name)
+{
+	struct node_t *node;
+	struct gcov_info *info;
+
+	list_for_each_entry(node, &all_head, all) {
+		info = get_node_info(node);
+		if (info && (strcmp(info->filename, name) == 0))
+			return node;
+	}
+
+	return NULL;
+}
+
+static void remove_node(struct node_t *node);
+
+static ssize_t gcov_seq_write(struct file *file, const char __user *addr,
+			      size_t len, loff_t *pos)
+{
+	struct seq_file *seq = file->private_data;
+	struct gcov_info *info = gcov_iter_get_info(seq->private);
+	struct node_t *node;
+
+	mutex_lock(&node_lock);
+	node = get_node_by_name(info->filename);
+	if (node) {
+		/* Reset counts or remove node for unloaded modules. */
+		if (node->ghost)
+			remove_node(node);
+		else
+			gcov_info_reset(node->info);
+	}
+	/* Reset counts for open file. */
+	gcov_info_reset(info);
+	mutex_unlock(&node_lock);
+
+	return len;
+}
+
+static char *build_target(const char *dir, const char *rel, const char *ext)
+{
+	char *target;
+	char *old_ext;
+
+	if (dir) {
+		/* DIR + '/' + REL + '.' + EXT + '\0' */
+		target = kmalloc(strlen(dir) + strlen(rel) + strlen(ext) + 3,
+				 GFP_KERNEL);
+		if (!target)
+			return NULL;
+		sprintf(target, "%s/%s", dir, rel);
+	} else {
+		/* REL + '.' + EXT + '\0' */
+		target = kmalloc(strlen(rel) + strlen(ext) + 2, GFP_KERNEL);
+		if (!target)
+			return NULL;
+		sprintf(target, "%s", rel);
+	}
+	old_ext = strrchr(target, '.');
+	if (!old_ext)
+		old_ext = target + strlen(target);
+	sprintf(old_ext, ".%s", ext);
+
+	return target;
+}
+
+char *get_link_target(const char *filename, const struct gcov_link_t *ext)
+{
+	const char *rel;
+	char *result;
+
+	if (strncmp(filename, objtree, strlen(objtree)) == 0) {
+		rel = filename + strlen(objtree) + 1;
+		if (ext->dir == src_tree)
+			result = build_target(srctree, rel, ext->ext);
+		else
+			result = build_target(objtree, rel, ext->ext);
+	} else {
+		/* External compilation. */
+		result = build_target(NULL, filename, ext->ext);
+	}
+	return result;
+}
+
+static void add_links(struct node_t *node, struct dentry *parent)
+{
+	char *basename;
+	char *target;
+	int num;
+	int i;
+
+	for (num = 0; gcov_link[num].ext; num++)
+		/* Nothing. */;
+	node->links = kcalloc(num, sizeof(struct dentry *), GFP_KERNEL);
+	if (!node->links)
+		return;
+	for (i = 0; i < num; i++) {
+		target = get_link_target(get_node_info(node)->filename,
+					 &gcov_link[i]);
+		if (!target)
+			goto out_err;
+		basename = strrchr(target, '/');
+		if (!basename)
+			goto out_err;
+		basename++;
+		node->links[i] = debugfs_create_symlink(basename, parent,
+							target);
+		if (!node->links[i])
+			goto out_err;
+		kfree(target);
+	}
+
+	return;
+out_err:
+	kfree(target);
+	while (i-- > 0)
+		debugfs_remove(node->links[i]);
+	kfree(node->links);
+	node->links = NULL;
+}
+
+static struct file_operations data_fops = {
+	.open = gcov_seq_open,
+	.release = gcov_seq_release,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.write = gcov_seq_write,
+};
+
+static void init_node(struct node_t *node, struct gcov_info *info,
+		      const char *name)
+{
+	INIT_LIST_HEAD(&node->list);
+	INIT_LIST_HEAD(&node->children);
+	INIT_LIST_HEAD(&node->all);
+	node->info = info;
+	if (name)
+		strcpy(node->name, name);
+}
+
+static struct node_t *new_node(struct node_t *parent, struct gcov_info *info,
+			       const char *name)
+{
+	struct node_t *node;
+
+	node = kzalloc(sizeof(struct node_t) + strlen(name) + 1, GFP_KERNEL);
+	if (!node) {
+		printk(KERN_WARNING TAG "out of memory\n");
+		return NULL;
+	}
+	init_node(node, info, name);
+	if (info) {
+		node->dentry = debugfs_create_file(node->name, 0600,
+					parent->dentry, node, &data_fops);
+	} else
+		node->dentry = debugfs_create_dir(node->name, parent->dentry);
+	if (!node->dentry) {
+		printk(KERN_WARNING TAG "could not create file\n");
+		kfree(node);
+		return NULL;
+	}
+	if (info)
+		add_links(node, parent->dentry);
+	list_add(&node->list, &parent->children);
+	list_add(&node->all, &all_head);
+
+	return node;
+}
+
+static void remove_links(struct node_t *node)
+{
+	int i;
+
+	if (!node->links)
+		return;
+	for (i = 0; gcov_link[i].ext; i++)
+		debugfs_remove(node->links[i]);
+	kfree(node->links);
+	node->links = NULL;
+}
+
+static void release_node(struct node_t *node)
+{
+	list_del(&node->list);
+	list_del(&node->all);
+	debugfs_remove(node->dentry);
+	remove_links(node);
+	kfree(node->ghost);
+	kfree(node);
+}
+
+static void remove_node(struct node_t *node)
+{
+	struct node_t *parent;
+
+	while ((node != &root_node) && list_empty(&node->children)) {
+		parent = node->parent;
+		release_node(node);
+		node = parent;
+	}
+}
+
+static struct node_t *get_child_by_name(struct node_t *parent, const char *name)
+{
+	struct node_t *node;
+
+	list_for_each_entry(node, &parent->children, list) {
+		if (strcmp(node->name, name) == 0)
+			return node;
+	}
+
+	return NULL;
+}
+
+static ssize_t reset_write(struct file *file, const char __user *addr,
+			   size_t len, loff_t *pos)
+{
+	struct node_t *node;
+	struct node_t *r;
+
+	mutex_lock(&node_lock);
+	list_for_each_entry_safe(node, r, &all_head, all) {
+		if (node->info)
+			gcov_info_reset(node->info);
+		else
+			remove_node(node);
+	}
+	mutex_unlock(&node_lock);
+
+	return len;
+}
+
+static ssize_t reset_read(struct file *file, char __user *addr, size_t len,
+			  loff_t *pos)
+{
+	/* Allow read operation so that a recursive copy won't fail. */
+	return 0;
+}
+
+static struct file_operations reset_fops = {
+	.write = reset_write,
+	.read = reset_read,
+};
+
+static void add_node(struct gcov_info *info)
+{
+	char *filename;
+	char *curr;
+	char *next;
+	struct node_t *parent;
+	struct node_t *node;
+
+	filename = kstrdup(info->filename, GFP_KERNEL);
+	if (!filename)
+		return;
+	parent = &root_node;
+	/* Create path nodes. */
+	for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) {
+		if (curr == next)
+			continue;
+		*next = 0;
+		node = get_child_by_name(parent, curr);
+		if (!node) {
+			node = new_node(parent, NULL, curr);
+			if (!node)
+				goto out_err;
+		}
+		parent = node;
+	}
+	/* Create file node. */
+	node = new_node(parent, info, curr);
+	if (node)
+		goto out;
+out_err:
+	remove_node(parent);
+out:
+	kfree(filename);
+}
+
+static int ghost_node(struct node_t *node)
+{
+	node->ghost = gcov_info_dup(node->info);
+	if (!node->ghost) {
+		printk(KERN_WARNING TAG "could not save data for '%s' (out of "
+		       "memory)\n", node->info->filename);
+		return -ENOMEM;
+	}
+	node->info = NULL;
+
+	return 0;
+}
+
+static void revive_node(struct node_t *node, struct gcov_info *info)
+{
+	if (gcov_info_is_compatible(node->ghost, info))
+		gcov_info_add(info, node->ghost);
+	else {
+		printk(KERN_WARNING TAG "could not add data for '%s' "
+		       "(incompatible data)\n", info->filename);
+	}
+	kfree(node->ghost);
+	node->ghost = NULL;
+	node->info = info;
+}
+
+static void gcov_cb(enum gcov_action action, struct gcov_info *info)
+{
+	struct node_t *node;
+
+	mutex_lock(&node_lock);
+	node = get_node_by_name(info->filename);
+	switch (action) {
+	case gcov_add:
+		if (!node) {
+			add_node(info);
+			break;
+		}
+		if (gcov_persist)
+			revive_node(node, info);
+		else
+			printk(KERN_WARNING TAG "could not add '%s' "
+			       "(already exists)\n", info->filename);
+		break;
+	case gcov_remove:
+		if (!node) {
+			printk(KERN_WARNING TAG "could not remove '%s' (not "
+			       "found)\n", info->filename);
+			break;
+		}
+		if (gcov_persist) {
+			if (!ghost_node(node))
+				break;
+		}
+		remove_node(node);
+		break;
+	}
+	mutex_unlock(&node_lock);
+}
+
+static __init int gcov_fs_init(void)
+{
+	int rc;
+
+	INIT_LIST_HEAD(&all_head);
+	init_node(&root_node, NULL, NULL);
+	root_node.dentry = debugfs_create_dir("gcov", NULL);
+	if (!root_node.dentry) {
+		printk(KERN_WARNING TAG "could not create root dir\n");
+		rc = -EIO;
+		goto out_remove;
+	}
+	reset_dentry = debugfs_create_file("reset", 0600, root_node.dentry,
+					   NULL, &reset_fops);
+	if (!reset_dentry) {
+		printk(KERN_WARNING TAG "could not create reset file\n");
+		rc = -EIO;
+		goto out_remove;
+	}
+	rc = gcov_register_callback(gcov_cb);
+	if (rc) {
+		printk(KERN_WARNING TAG "could not register callback (rc=%d)\n",
+		       rc);
+		goto out_remove;
+	}
+
+	return 0;
+out_remove:
+	if (reset_dentry)
+		debugfs_remove(reset_dentry);
+	if (root_node.dentry)
+		debugfs_remove(root_node.dentry);
+
+	return rc;
+}
+__initcall(gcov_fs_init);
Index: linux-2.6.26-rc1/kernel/gcov/gcc_3_4.c
===================================================================
--- /dev/null
+++ linux-2.6.26-rc1/kernel/gcov/gcc_3_4.c
@@ -0,0 +1,374 @@
+/*
+ *  Handling of profiling data.
+ *
+ *    Copyright IBM Corp. 2008
+ *    Author(s): Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
+ *
+ *    Uses gcc-internal data definitions.
+ */
+
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/seq_file.h>
+#include "gcov.h"
+
+const struct gcov_link_t gcov_link[] = {
+	{ src_tree, "c" },
+	{ obj_tree, "gcno" },
+	{ 0, NULL},
+};
+
+/* Determine whether counter type is active in info. */
+static int counter_active(struct gcov_info *info, unsigned int type)
+{
+	return (1 << type) & info->ctr_mask;
+}
+
+/* Return the number of active counter types for info. */
+static unsigned int num_counter_active(struct gcov_info *info)
+{
+	unsigned int i;
+	unsigned int result = 0;
+
+	for (i = 0; i < GCOV_COUNTERS; i++)
+		if (counter_active(info, i))
+			result++;
+	return result;
+}
+
+/**
+ * gcov_info_reset - reset profiling data
+ * @info: profiling data address
+ */
+void gcov_info_reset(struct gcov_info *info)
+{
+	struct gcov_ctr_info *ctr = info->counts;
+	unsigned int i;
+
+	for (i = 0; i < GCOV_COUNTERS; i++) {
+		if (counter_active(info, i)) {
+			memset(ctr->values, 0, ctr->num * sizeof(gcov_type));
+			ctr++;
+		}
+	}
+}
+
+/**
+ * gcov_info_is_compatible - return whether profiling data can be added
+ * @info1: address of first profiling data set
+ * @info2: address of second profiling data set
+ *
+ * Returns non-zero if profiling data can be added, zero otherwise.
+ */
+int gcov_info_is_compatible(struct gcov_info *info1, struct gcov_info *info2)
+{
+	return (info1->stamp == info2->stamp);
+}
+
+/**
+ * gcov_info_add - add up profiling data
+ * @dest: address of profiling data to which data is added
+ * @source: address of profiling data which is added
+ *
+ * Adds profiling counts of @source to @dest.
+ */
+void gcov_info_add(struct gcov_info *dest, struct gcov_info *source)
+{
+	unsigned int i;
+	unsigned int j;
+
+	for (i = 0; i < num_counter_active(dest); i++)
+		for (j = 0; j < dest->counts[i].num; j++)
+			dest->counts[i].values[j] +=
+				source->counts[i].values[j];
+}
+
+/* Get size of function info entry. */
+static size_t get_fn_size(struct gcov_info *info)
+{
+	size_t size;
+
+	size = sizeof(struct gcov_fn_info) + num_counter_active(info) *
+	       sizeof(unsigned int);
+	if (__alignof__(struct gcov_fn_info) > sizeof(unsigned int))
+		size = ALIGN(size, __alignof__(struct gcov_fn_info));
+	return size;
+}
+
+/* Get address of function info entry. */
+static struct gcov_fn_info *get_fn_info(struct gcov_info *info, unsigned int fn)
+{
+	return (struct gcov_fn_info *)
+		((char *) info->functions + fn * get_fn_size(info));
+}
+
+/**
+ * gcov_info_dup - duplicate profiling data
+ * @info: address of profiling data to duplicate
+ *
+ * Return the address of the newly allocated duplicate on success, %NULL on
+ * error.
+ */
+struct gcov_info *gcov_info_dup(struct gcov_info *info)
+{
+	struct gcov_info *result;
+	size_t len;
+	unsigned int active;
+	unsigned int i;
+	char *name;
+	struct gcov_fn_info *func;
+	gcov_type *values;
+
+	/* Allocate memory for struct gcov_info */
+	active = num_counter_active(info);
+	len = sizeof(struct gcov_info) + sizeof(struct gcov_ctr_info) * active +
+	      strlen(info->filename) + 1;
+	for (i = 0; i < active; i++)
+		len += sizeof(gcov_type) * info->counts[i].num;
+	result = kzalloc(len, GFP_KERNEL);
+	if (!result)
+		return NULL;
+	/* Allocate memory for array of struct gcov_fn_info */
+	len = info->n_functions * get_fn_size(info);
+	func = kmalloc(len, GFP_KERNEL);
+	if (!func) {
+		kfree(result);
+		return NULL;
+	}
+	/* Copy function data */
+	memcpy(func, info->functions, len);
+	result->functions = func;
+	/* Copy counts */
+	values = (gcov_type *) &result->counts[active];
+	for (i = 0; i < active; i++) {
+		result->counts[i].num = info->counts[i].num;
+		result->counts[i].merge = info->counts[i].merge;
+		result->counts[i].values = values;
+		memcpy(values, info->counts[i].values,
+		       sizeof(gcov_type) * info->counts[i].num);
+		values += info->counts[i].num;
+	}
+	/* Copy rest */
+	result->stamp = info->stamp;
+	name = (char *) values;
+	strcpy(name, info->filename);
+	result->filename = name;
+	result->n_functions = info->n_functions;
+	result->ctr_mask = info->ctr_mask;
+
+	return result;
+}
+
+/**
+ * gcov_info_free - release memory for profiling data duplicate
+ * @info: address of profiling data to free
+ */
+void gcov_info_free(struct gcov_info *info)
+{
+	kfree(info->functions);
+	kfree(info);
+}
+
+struct type_info {
+	int num;
+	unsigned int offset;
+};
+
+struct iterator_t {
+	struct gcov_info *info;
+
+	int record;
+	unsigned int function;
+	unsigned int type;
+	unsigned int count;
+
+	int num_types;
+	struct type_info type_info[0];
+};
+
+static struct gcov_fn_info *get_func(struct iterator_t *iter)
+{
+	return get_fn_info(iter->info, iter->function);
+}
+
+static struct type_info *get_type(struct iterator_t *iter)
+{
+	return &iter->type_info[iter->type];
+}
+
+/**
+ * gcov_iter_new - allocate and initialize profiling data iterator
+ * @info: address of profiling data to be iterated
+ *
+ * Return address of iterator on success, %NULL otherwise.
+ */
+void *gcov_iter_new(struct gcov_info *info)
+{
+	struct iterator_t *iter;
+
+	iter = kzalloc(sizeof(struct iterator_t) +
+		       num_counter_active(info) * sizeof(struct type_info),
+		       GFP_KERNEL);
+	if (iter)
+		iter->info = info;
+
+	return iter;
+}
+
+/**
+ * gcov_iter_get_info - return address of profiling data for iterator
+ * @data: iterator address
+ */
+struct gcov_info *gcov_iter_get_info(void *data)
+{
+	struct iterator_t *iter = data;
+
+	return iter->info;
+}
+
+/**
+ * gcov_iter_start - reset iterator to starting position
+ * @data: iterator address
+ */
+void gcov_iter_start(void *data)
+{
+	struct iterator_t *iter = data;
+	int i;
+
+	iter->record = 0;
+	iter->function = 0;
+	iter->type = 0;
+	iter->count = 0;
+	iter->num_types = 0;
+	for (i = 0; i < GCOV_COUNTERS; i++) {
+		if (counter_active(iter->info, i)) {
+			iter->type_info[iter->num_types].num = i;
+			iter->type_info[iter->num_types++].offset = 0;
+		}
+	}
+}
+
+/**
+ * gcov_iter_next - advance iterator to next pos
+ * @data: iterator address
+ *
+ * Return zero if new position is valid, non-zero if iterator has reached end.
+ */
+int gcov_iter_next(void *data)
+{
+	struct iterator_t *iter = data;
+
+	switch (iter->record) {
+	case 0:
+	case 1:
+	case 3:
+	case 4:
+	case 5:
+	case 7:
+		/* Advance to next record */
+		iter->record++;
+		break;
+	case 9:
+		/* Advance to next count */
+		iter->count++;
+		/* fall through */
+	case 8:
+		if (iter->count < get_func(iter)->n_ctrs[iter->type]) {
+			iter->record = 9;
+			break;
+		}
+		/* Advance to next counter type */
+		get_type(iter)->offset += iter->count;
+		iter->count = 0;
+		iter->type++;
+		/* fall through */
+	case 6:
+		if (iter->type < iter->num_types) {
+			iter->record = 7;
+			break;
+		}
+		/* Advance to next function */
+		iter->type = 0;
+		iter->function++;
+		/* fall through */
+	case 2:
+		if (iter->function < iter->info->n_functions)
+			iter->record = 3;
+		else
+			iter->record = -1;
+		break;
+	}
+	/* Check for EOF. */
+	if (iter->record == -1)
+		return -EINVAL;
+	else
+		return 0;
+}
+
+static int seq_write_gcov_int(struct seq_file *seq, size_t size, u64 v)
+{
+	u32 data[2];
+
+	switch (size) {
+	case 8:
+		data[1] = (u32) (v >> 32);
+		/* fall through */
+	case 4:
+		data[0] = (u32) (v & 0xffffffffUL);
+		return seq_write(seq, data, size);
+	}
+	return -EINVAL;
+}
+
+/**
+ * gcov_iter_write - write data for current pos to seq_file
+ * @data: iterator address
+ * @seq: seq_file address
+ *
+ * Return zero on success, non-zero otherwise.
+ */
+int gcov_iter_write(void *data, struct seq_file *seq)
+{
+	struct iterator_t *iter = data;
+	int rc;
+
+	rc = -EINVAL;
+	switch (iter->record) {
+	case 0: /* file magic */
+		rc = seq_write_gcov_int(seq, 4, GCOV_DATA_MAGIC);
+		break;
+	case 1: /* gcov_version */
+		rc = seq_write_gcov_int(seq, 4, gcov_version);
+		break;
+	case 2: /* time stamp */
+		rc = seq_write_gcov_int(seq, 4, iter->info->stamp);
+		break;
+	case 3: /* function tag */
+		rc = seq_write_gcov_int(seq, 4, GCOV_TAG_FUNCTION);
+		break;
+	case 4: /* function tag length */
+		rc = seq_write_gcov_int(seq, 4, 2);
+		break;
+	case 5: /* function ident*/
+		rc = seq_write_gcov_int(seq, 4, get_func(iter)->ident);
+		break;
+	case 6: /* function checksum */
+		rc = seq_write_gcov_int(seq, 4, get_func(iter)->checksum);
+		break;
+	case 7: /* count tag */
+		rc = seq_write_gcov_int(seq, 4,
+			GCOV_TAG_FOR_COUNTER(get_type(iter)->num));
+		break;
+	case 8: /* count length */
+		rc = seq_write_gcov_int(seq, 4,
+				get_func(iter)->n_ctrs[iter->type] * 2);
+		break;
+	case 9: /* count */
+		rc = seq_write_gcov_int(seq, 8,
+			iter->info->counts[iter->type].
+				values[iter->count + get_type(iter)->offset]);
+		break;
+	}
+	return rc;
+}



             reply	other threads:[~2008-05-05 15:25 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-05-05 15:24 Peter Oberparleiter [this message]
2008-05-06  4:30 ` [RFC PATCH 5/6] gcov: add gcov profiling infrastructure Andrew Morton
2008-05-06  5:43 ` Andrew Morton
2008-05-07 10:57   ` Peter Oberparleiter
2008-05-07 15:26     ` Andrew Morton

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=481F26BE.2060706@de.ibm.com \
    --to=peter.oberparleiter@de.ibm.com \
    --cc=akpm@linux-foundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ltp-coverage@lists.sourceforge.net \
    --cc=ltp-list@lists.sourceforge.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.