patch-1.3.5 linux/fs/binfmt_elf.c

Next file: linux/fs/devices.c
Previous file: linux/drivers/scsi/wd7000.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.4/linux/fs/binfmt_elf.c linux/fs/binfmt_elf.c
@@ -18,6 +18,7 @@
 #endif
 
 #include <linux/fs.h>
+#include <linux/stat.h>
 #include <linux/sched.h>
 #include <linux/mm.h>
 #include <linux/mman.h>
@@ -31,6 +32,7 @@
 #include <linux/malloc.h>
 #include <linux/shm.h>
 #include <linux/personality.h>
+#include <linux/elfcore.h>
 
 #include <asm/segment.h>
 #include <asm/pgtable.h>
@@ -48,12 +50,14 @@
 
 static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs);
 static int load_elf_library(int fd);
+static int elf_core_dump(long signr, struct pt_regs * regs);
+extern int dump_fpu (elf_fpregset_t *);
 
 struct linux_binfmt elf_format = {
 #ifndef MODULE
-	NULL, NULL, load_elf_binary, load_elf_library, NULL
+	NULL, NULL, load_elf_binary, load_elf_library, elf_core_dump
 #else
-	NULL, &mod_use_count_, load_elf_binary, load_elf_library, NULL
+	NULL, &mod_use_count_, load_elf_binary, load_elf_library, elf_core_dump
 #endif
 };
 
@@ -759,6 +763,411 @@
 	return 0;
 }
 
+/*
+ * ELF core dumper
+ *
+ * Modelled on fs/exec.c:aout_core_dump()
+ * Jeremy Fitzhardinge <jeremy@sw.oz.au>
+ */
+/*
+ * These are the only things you should do on a core-file: use only these
+ * functions to write out all the necessary info.
+ */
+static int dump_write(struct file *file, void *addr, int nr)
+{
+	return file->f_op->write(file->f_inode, file, addr, nr) == nr;
+}
+
+static int dump_seek(struct file *file, off_t off)
+{
+	if (file->f_op->lseek) {
+		if (file->f_op->lseek(file->f_inode, file, off, 0) != off)
+			return 0;
+	} else
+		file->f_pos = off;
+	return 1;
+}
+
+/*
+ * Decide whether a segment is worth dumping; default is yes to be
+ * sure (missing info is worse than too much; etc).
+ * Personally I'd include everything, and use the coredump limit...
+ *
+ * I think we should skip something. But I am not sure how. H.J.
+ */
+static inline int maydump(struct vm_area_struct *vma)
+{
+#if 1
+	if (vma->vm_flags & (VM_WRITE|VM_GROWSUP|VM_GROWSDOWN))
+		return 1;
+	if (vma->vm_flags & (VM_READ|VM_EXEC|VM_EXECUTABLE|VM_SHARED))
+		return 0;
+#endif
+	return 1;
+}
+
+#define roundup(x, y)  ((((x)+((y)-1))/(y))*(y))
+
+/* An ELF note in memory */
+struct memelfnote
+{
+	char *name;
+	int type;
+	unsigned int datasz;
+	void *data;
+};
+
+static int notesize(struct memelfnote *en)
+{
+	int sz;
+	
+	sz = sizeof(struct elf_note);
+	sz += roundup(strlen(en->name), 4);
+	sz += roundup(en->datasz, 4);
+	
+	return sz;
+}
+
+/* #define DEBUG */
+
+#ifdef DEBUG
+static void dump_regs(const char *str, elf_greg_t *r)
+{
+	int i;
+	static const char *regs[] = { "ebx", "ecx", "edx", "esi", "edi", "ebp",
+					      "eax", "ds", "es", "fs", "gs",
+					      "orig_eax", "eip", "cs",
+					      "efl", "uesp", "ss"};
+	printk("Registers: %s\n", str);
+
+	for(i = 0; i < ELF_NGREG; i++)
+	{
+		unsigned long val = r[i];
+		printk("   %-2d %-5s=%08lx %lu\n", i, regs[i], val, val);
+	}
+}
+#endif
+
+#define DUMP_WRITE(addr, nr)	\
+	do { if (!dump_write(file, (addr), (nr))) return 0; } while(0)
+#define DUMP_SEEK(off)	\
+	do { if (!dump_seek(file, (off))) return 0; } while(0)
+
+static int writenote(struct memelfnote *men, struct file *file)
+{
+	struct elf_note en;
+
+	en.n_namesz = strlen(men->name);
+	en.n_descsz = men->datasz;
+	en.n_type = men->type;
+
+	DUMP_WRITE(&en, sizeof(en));
+	DUMP_WRITE(men->name, en.n_namesz);
+	/* XXX - cast from long long to long to avoid need for libgcc.a */
+	DUMP_SEEK(roundup((unsigned long)file->f_pos, 4));	/* XXX */
+	DUMP_WRITE(men->data, men->datasz);
+	DUMP_SEEK(roundup((unsigned long)file->f_pos, 4));	/* XXX */
+	
+	return 1;
+}
+#undef DUMP_WRITE
+#undef DUMP_SEEK
+
+#define DUMP_WRITE(addr, nr)	\
+	if (!dump_write(&file, (addr), (nr))) \
+		goto close_coredump;
+#define DUMP_SEEK(off)	\
+	if (!dump_seek(&file, (off))) \
+		goto close_coredump;
+/*
+ * Actual dumper
+ *
+ * This is a two-pass process; first we find the offsets of the bits,
+ * and then they are actually written out.  If we run out of core limit
+ * we just truncate.
+ */
+static int elf_core_dump(long signr, struct pt_regs * regs)
+{
+	int has_dumped = 0;
+	struct file file;
+	struct inode *inode;
+	unsigned short fs;
+	char corefile[6+sizeof(current->comm)];
+	int segs;
+	int i;
+	size_t size;
+	struct vm_area_struct *vma;
+	struct elfhdr elf;
+	off_t offset = 0, dataoff;
+	int limit = current->rlim[RLIMIT_CORE].rlim_cur;
+	int numnote = 4;
+	struct memelfnote notes[4];
+	struct elf_prstatus prstatus;	/* NT_PRSTATUS */
+	elf_fpregset_t fpu;		/* NT_PRFPREG */
+	struct elf_prpsinfo psinfo;	/* NT_PRPSINFO */
+	
+	if (!current->dumpable || limit < PAGE_SIZE)
+		return 0;
+	current->dumpable = 0;
+
+#ifndef CONFIG_BINFMT_ELF
+	MOD_INC_USE_COUNT;
+#endif
+
+	/* Count what's needed to dump, up to the limit of coredump size */
+	segs = 0;
+	size = 0;
+	for(vma = current->mm->mmap; vma != NULL; vma = vma->vm_next) {
+		int sz = vma->vm_end-vma->vm_start;
+		
+		if (!maydump(vma))
+			continue;
+
+		if (size+sz > limit)
+			break;
+		
+		segs++;
+		size += sz;
+	}
+#ifdef DEBUG
+	printk("elf_core_dump: %d segs taking %d bytes\n", segs, size);
+#endif
+
+	/* Set up header */
+	memcpy(elf.e_ident, ELFMAG, SELFMAG);
+	elf.e_ident[EI_CLASS] = ELFCLASS32;
+	elf.e_ident[EI_DATA] = ELFDATA2LSB;
+	elf.e_ident[EI_VERSION] = EV_CURRENT;
+	memset(elf.e_ident+EI_PAD, 0, EI_NIDENT-EI_PAD);
+	
+	elf.e_type = ET_CORE;
+	elf.e_machine = EM_386;
+	elf.e_version = EV_CURRENT;
+	elf.e_entry = 0;
+	elf.e_phoff = sizeof(elf);
+	elf.e_shoff = 0;
+	elf.e_flags = 0;
+	elf.e_ehsize = sizeof(elf);
+	elf.e_phentsize = sizeof(struct elf_phdr);
+	elf.e_phnum = segs+1;		/* Include notes */
+	elf.e_shentsize = 0;
+	elf.e_shnum = 0;
+	elf.e_shstrndx = 0;
+	
+	fs = get_fs();
+	set_fs(KERNEL_DS);
+	memcpy(corefile,"core.",5);
+#if 0
+	memcpy(corefile+5,current->comm,sizeof(current->comm));
+#else
+	corefile[4] = '\0';
+#endif
+	if (open_namei(corefile,O_CREAT | 2 | O_TRUNC,0600,&inode,NULL)) {
+		inode = NULL;
+		goto end_coredump;
+	}
+	if (!S_ISREG(inode->i_mode))
+		goto end_coredump;
+	if (!inode->i_op || !inode->i_op->default_file_ops)
+		goto end_coredump;
+	file.f_mode = 3;
+	file.f_flags = 0;
+	file.f_count = 1;
+	file.f_inode = inode;
+	file.f_pos = 0;
+	file.f_reada = 0;
+	file.f_op = inode->i_op->default_file_ops;
+	if (file.f_op->open)
+		if (file.f_op->open(inode,&file))
+			goto end_coredump;
+	if (!file.f_op->write)
+		goto close_coredump;
+	has_dumped = 1;
+
+	DUMP_WRITE(&elf, sizeof(elf));
+	offset += sizeof(elf);				/* Elf header */
+	offset += (segs+1) * sizeof(struct elf_phdr);	/* Program headers */
+
+	/*
+	 * Set up the notes in similar form to SVR4 core dumps made
+	 * with info from their /proc.
+	 */
+	memset(&psinfo, 0, sizeof(psinfo));
+	memset(&prstatus, 0, sizeof(prstatus));
+
+	notes[0].name = "CORE";
+	notes[0].type = NT_PRSTATUS;
+	notes[0].datasz = sizeof(prstatus);
+	notes[0].data = &prstatus;
+	prstatus.pr_info.si_signo = prstatus.pr_cursig = signr;
+	prstatus.pr_sigpend = current->signal;
+	prstatus.pr_sighold = current->blocked;
+	psinfo.pr_pid = prstatus.pr_pid = current->pid;
+	psinfo.pr_ppid = prstatus.pr_ppid = current->p_pptr->pid;
+	psinfo.pr_pgrp = prstatus.pr_pgrp = current->pgrp;
+	psinfo.pr_sid = prstatus.pr_sid = current->session;
+	prstatus.pr_utime.tv_sec = CT_TO_SECS(current->utime);
+	prstatus.pr_utime.tv_usec = CT_TO_USECS(current->utime);
+	prstatus.pr_stime.tv_sec = CT_TO_SECS(current->stime);
+	prstatus.pr_stime.tv_usec = CT_TO_USECS(current->stime);
+	prstatus.pr_cutime.tv_sec = CT_TO_SECS(current->cutime);
+	prstatus.pr_cutime.tv_usec = CT_TO_USECS(current->cutime);
+	prstatus.pr_cstime.tv_sec = CT_TO_SECS(current->cstime);
+	prstatus.pr_cstime.tv_usec = CT_TO_USECS(current->cstime);
+	if (sizeof(elf_gregset_t) != sizeof(struct pt_regs))
+	{
+		printk("sizeof(elf_gregset_t) (%d) != sizeof(struct pt_regs) (%d)\n",
+			sizeof(elf_gregset_t), sizeof(struct pt_regs));
+	}
+	else
+		*(struct pt_regs *)&prstatus.pr_reg = *regs;
+	
+#ifdef DEBUG
+	dump_regs("Passed in regs", (elf_greg_t *)regs);
+	dump_regs("prstatus regs", (elf_greg_t *)&prstatus.pr_reg);
+#endif
+
+	notes[1].name = "CORE";
+	notes[1].type = NT_PRPSINFO;
+	notes[1].datasz = sizeof(psinfo);
+	notes[1].data = &psinfo;
+	psinfo.pr_state = current->state;
+	psinfo.pr_sname = (current->state < 0 || current->state > 5) ? '.' : "RSDZTD"[current->state];
+	psinfo.pr_zomb = psinfo.pr_sname == 'Z';
+	psinfo.pr_nice = current->priority-15;
+	psinfo.pr_flag = current->flags;
+	psinfo.pr_uid = current->uid;
+	psinfo.pr_gid = current->gid;
+	{
+		int i, len;
+
+		set_fs(fs);
+		
+		len = current->mm->arg_end - current->mm->arg_start;
+		len = len >= ELF_PRARGSZ ? ELF_PRARGSZ : len;
+		memcpy_fromfs(&psinfo.pr_psargs,
+			      (const char *)current->mm->arg_start, len);
+		for(i = 0; i < len; i++)
+			if (psinfo.pr_psargs[i] == 0)
+				psinfo.pr_psargs[i] = ' ';
+		psinfo.pr_psargs[len] = 0;
+
+		set_fs(KERNEL_DS);
+	}
+	strncpy(psinfo.pr_fname, current->comm, sizeof(psinfo.pr_fname));
+
+	notes[2].name = "CORE";
+	notes[2].type = NT_TASKSTRUCT;
+	notes[2].datasz = sizeof(*current);
+	notes[2].data = current;
+	
+	/* Try to dump the fpu. */
+	prstatus.pr_fpvalid = dump_fpu (&fpu);
+	if (!prstatus.pr_fpvalid)
+	{
+		numnote--;
+	}
+	else
+	{
+		notes[3].name = "CORE";
+		notes[3].type = NT_PRFPREG;
+		notes[3].datasz = sizeof(fpu);
+		notes[3].data = &fpu;
+	}
+	
+	/* Write notes phdr entry */
+	{
+		struct elf_phdr phdr;
+		int sz = 0;
+
+		for(i = 0; i < numnote; i++)
+			sz += notesize(&notes[i]);
+		
+		phdr.p_type = PT_NOTE;
+		phdr.p_offset = offset;
+		phdr.p_vaddr = 0;
+		phdr.p_paddr = 0;
+		phdr.p_filesz = sz;
+		phdr.p_memsz = 0;
+		phdr.p_flags = 0;
+		phdr.p_align = 0;
+
+		offset += phdr.p_filesz;
+		DUMP_WRITE(&phdr, sizeof(phdr));
+	}
+
+	/* Page-align dumped data */
+	dataoff = offset = roundup(offset, PAGE_SIZE);
+	
+	/* Write program headers for segments dump */
+	for(vma = current->mm->mmap, i = 0;
+		i < segs && vma != NULL; vma = vma->vm_next) {
+		struct elf_phdr phdr;
+		size_t sz;
+
+		if (!maydump(vma))
+			continue;
+		i++;
+
+		sz = vma->vm_end - vma->vm_start;
+		
+		phdr.p_type = PT_LOAD;
+		phdr.p_offset = offset;
+		phdr.p_vaddr = vma->vm_start;
+		phdr.p_paddr = 0;
+		phdr.p_filesz = sz;
+		phdr.p_memsz = sz;
+		offset += sz;
+		phdr.p_flags = vma->vm_flags & VM_READ ? PF_R : 0;
+		if (vma->vm_flags & VM_WRITE) phdr.p_flags |= PF_W;
+		if (vma->vm_flags & VM_EXEC) phdr.p_flags |= PF_X;
+		phdr.p_align = PAGE_SIZE;
+
+		DUMP_WRITE(&phdr, sizeof(phdr));
+	}
+
+	for(i = 0; i < numnote; i++)
+		if (!writenote(&notes[i], &file))
+			goto close_coredump;
+	
+	set_fs(fs);
+
+	DUMP_SEEK(dataoff);
+	
+	for(i = 0, vma = current->mm->mmap;
+	    i < segs && vma != NULL;
+	    vma = vma->vm_next) {
+		unsigned long addr = vma->vm_start;
+		unsigned long len = vma->vm_end - vma->vm_start;
+		
+		if (!maydump(vma))
+			continue;
+		i++;
+#ifdef DEBUG
+		printk("elf_core_dump: writing %08lx %lx\n", addr, len);
+#endif
+		DUMP_WRITE((void *)addr, len);
+	}
+
+	if ((off_t) file.f_pos != offset) {
+		/* Sanity check */
+		printk("elf_core_dump: file.f_pos (%ld) != offset (%ld)\n",
+		       (off_t) file.f_pos, offset);
+	}
+
+ close_coredump:
+	if (file.f_op->release)
+		file.f_op->release(inode,&file);
+
+ end_coredump:
+	set_fs(fs);
+	iput(inode);
+#ifndef CONFIG_BINFMT_ELF
+	MOD_DEC_USE_COUNT;
+#endif
+	return has_dumped;
+}
+
 #ifdef MODULE
 char kernel_version[] = UTS_RELEASE;
 
@@ -770,6 +1179,7 @@
 	register_binfmt(&elf_format);
 	return 0;
 }
+
 
 void cleanup_module( void) {
 	

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this