[PATCH 3/3] Plugin the core-dumper

Ananth N Mavinakayanahalli ananth at in.ibm.com
Thu Jul 30 09:57:48 UTC 2009


Plugin the actual worker core-dumper routine.

This is possibly a naive implementation, and looks very similar to
do_coredump(), with certain subtle differences:

- We currently don't honour rlimits (should we?). The coredump_filter
  though, is honoured.
- We build the core_state and core_thread chains on the fly.
- No pipe support. The core is saved in the format core.<pid>.<timestamp>
  where the timestamp is the ktime_get() value when the dump occurs.

Certainly, there are assumptions I've made that may be incorrect. The
prototype works for the most part. Only occasionally, a subtle race
which am yet to determine, causes a failure. As with fatal dumps, gdb is
able to decode the core.


Signed-off-by: Ananth N Mavinakayanahalli <ananth at in.ibm.com>
---
 fs/proc/proc_gencore.c |  144 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 144 insertions(+)

Index: utrace-13jul/fs/proc/proc_gencore.c
===================================================================
--- utrace-13jul.orig/fs/proc/proc_gencore.c
+++ utrace-13jul/fs/proc/proc_gencore.c
@@ -19,6 +19,7 @@
  */
 
 #include <linux/seq_file.h>
+#include <linux/binfmts.h>
 #include <linux/utrace.h>
 #include "internal.h"
 
@@ -47,14 +48,153 @@ struct core_task {
 	bool quiesced;
 };
 
+static void cleanup_core_state(struct core_state *core_state)
+{
+	struct core_thread *core_thread, *temp;
+
+	if (!core_state)
+		return;
+
+	core_thread = core_state->dumper.next;
+	while (core_thread != NULL) {
+		temp = core_thread->next;
+		kfree(core_thread);
+		core_thread = temp;
+	}
+	kfree(core_state);
+}
+
+static struct core_state *build_core_state(struct core_proc *core_proc)
+{
+	struct task_struct *t = current;
+	struct core_task *core_task, *temp;
+	struct core_state *core_state;
+
+	if (!core_proc)
+		return ERR_PTR(-EINVAL);
+
+	/*
+	 * We already know all threads have either been quiesced or blocked
+	 * in the kernel. Even for threads bloced in the kernel, we know for
+	 * sure that they'll not run userspace before quiescing.
+	 *
+	 * If this is a single-threaded process and its already exiting,
+	 * we still hold the mmap_sem and we'll also see it in core_exit.
+	 */
+	core_state = kzalloc(sizeof(*core_state), GFP_KERNEL);
+	if (!core_state)
+		return ERR_PTR(-ENOMEM);
+
+	core_state->dumper.task = t;
+	core_state->dumper.next = NULL;
+	t->mm->core_state = core_state;
+	/* Make sure this is visible to do_exit */
+	wmb();
+
+	list_for_each_entry_safe(core_task, temp, &core_proc->list, list) {
+		struct core_thread *core_thread;
+
+		if (core_task->task == t)
+			continue;
+		core_thread = kzalloc(sizeof(*core_thread), GFP_KERNEL);
+		if (!core_thread) {
+			cleanup_core_state(core_state);
+			return ERR_PTR(-ENOMEM);
+		}
+		core_thread->task = core_task->task;
+		core_thread->next = xchg(&core_state->dumper.next,
+				core_thread);
+	}
+	return core_state;
+}
+
+/*
+ * A 0 or negative return => failure.
+ * elf_core_dump returns 1 on SUCCESS
+ */
+static int write_core(struct core_proc *core_proc)
+{
+	struct core_state *core_state;
+	char corename[CORENAME_MAX_SIZE + 1];
+	struct mm_struct *mm = current->mm;
+	struct linux_binfmt *binfmt;
+	struct inode *inode;
+	struct file *file;
+	int retval = -1;
+	int flag = 0;
+
+	binfmt = current->binfmt;
+	if (!core_proc || !binfmt || !binfmt->core_dump)
+		return -EINVAL;
+
+	down_write(&mm->mmap_sem);
+	if (mm->core_state || !get_dumpable(mm)) {
+		up_write(&mm->mmap_sem);
+		return -EALREADY;
+	}
+
+	core_state = build_core_state(core_proc);
+	if (IS_ERR(core_state)) {
+		up_write(&mm->mmap_sem);
+		return PTR_ERR(core_state);
+	}
+
+	up_write(&mm->mmap_sem);
+
+	/* For now, core files will have the core.<pid>.timestamp format */
+	memset(corename, 0x00, (CORENAME_MAX_SIZE + 1));
+	strncpy(corename, "core.", strlen("core."));
+	snprintf((corename + strlen(corename)),
+			(CORENAME_MAX_SIZE - strlen(corename)),
+			"%d.", task_tgid_vnr(current));
+	snprintf((corename + strlen(corename)),
+			(CORENAME_MAX_SIZE - strlen(corename)),
+			"%lld", (ktime_to_us(ktime_get())));
+
+	file = filp_open(corename,
+			O_CREAT | 2 | O_NOFOLLOW | O_LARGEFILE | flag,
+			0600);
+	if (IS_ERR(file))
+		return PTR_ERR(file);
+	inode = file->f_path.dentry->d_inode;
+	if (inode->i_nlink > 1)
+		goto close_fail;
+	if (d_unhashed(file->f_path.dentry))
+		goto close_fail;
+	if (!S_ISREG(inode->i_mode))
+		goto close_fail;
+	if (inode->i_uid != current_fsuid())
+		goto close_fail;
+	if (!file->f_op)
+		goto close_fail;
+	if (!file->f_op->write)
+		goto close_fail;
+	if (do_truncate(file->f_path.dentry, 0, 0, file) != 0)
+		goto close_fail;
+
+	/* XXX coresize is currently hardcoded. need to fix that */
+	retval = binfmt->core_dump(0, NULL, file, 0xFFFFFFFF);
+
+close_fail:
+	filp_close(file, NULL);
+	cleanup_core_state(core_state);
+	current->mm->core_state = NULL;
+	wmb();
+	return retval;
+}
+
 static void cleanup_core_proc(struct core_proc *core_proc)
 {
 	struct core_task *core_task, *temp;
+	struct core_state *core_state = core_proc->tgid_task->mm->core_state;
 	int ret;
 
 	if (core_proc == NULL)
 		return;
 
+	if (core_state)
+		cleanup_core_state(core_state);
+
 	spin_lock(&core_proc->lock);
 	if (list_empty(&core_proc->list))
 		goto out;
@@ -215,6 +355,10 @@ static u32 core_quiesce(enum utrace_resu
 			atomic_read(&core_proc->num_threads)) {
 
 		/* All threads quiesced, do your thing :-) */
+		int ret = write_core(core_proc);
+		if (ret == -EALREADY)
+			/* Dump already in progress, hold thread */
+			return UTRACE_STOP;
 
 		/* Let everyone run, indicate we are done! */
 		quiesce_all_threads(core_proc, false);




More information about the utrace-devel mailing list