Skip to content
Snippets Groups Projects
binfmt_elf.c 60.8 KiB
Newer Older
	/* Try to dump the FPU. */
	info->prstatus->pr_fpvalid = elf_core_copy_task_fpregs(current, regs,
							       info->fpu);
	if (info->prstatus->pr_fpvalid)
		fill_note(info->notes + info->numnote++,
			  "CORE", NT_PRFPREG, sizeof(*info->fpu), info->fpu);
#ifdef ELF_CORE_COPY_XFPREGS
	if (elf_core_copy_task_xfpregs(current, info->xfpu))
		fill_note(info->notes + info->numnote++,
			  "LINUX", ELF_CORE_XFPREG_TYPE,
			  sizeof(*info->xfpu), info->xfpu);
#endif

	return 1;
}

static size_t get_note_info_size(struct elf_note_info *info)
{
	int sz = 0;
	int i;

	for (i = 0; i < info->numnote; i++)
		sz += notesize(info->notes + i);

	sz += info->thread_status_size;

	return sz;
}

static int write_note_info(struct elf_note_info *info,
Al Viro's avatar
Al Viro committed
			   struct coredump_params *cprm)
{
	int i;
	struct list_head *t;

	for (i = 0; i < info->numnote; i++)
Al Viro's avatar
Al Viro committed
		if (!writenote(info->notes + i, cprm))
			return 0;

	/* write out the thread status notes section */
	list_for_each(t, &info->thread_list) {
		struct elf_thread_status *tmp =
				list_entry(t, struct elf_thread_status, list);

		for (i = 0; i < tmp->num_notes; i++)
Al Viro's avatar
Al Viro committed
			if (!writenote(&tmp->notes[i], cprm))
				return 0;
	}

	return 1;
}

static void free_note_info(struct elf_note_info *info)
{
	while (!list_empty(&info->thread_list)) {
		struct list_head *tmp = info->thread_list.next;
		list_del(tmp);
		kfree(list_entry(tmp, struct elf_thread_status, list));
	}

	/* Free data possibly allocated by fill_files_note(): */
	if (info->notes_files)
		vfree(info->notes_files->data);
	kfree(info->prstatus);
	kfree(info->psinfo);
	kfree(info->notes);
	kfree(info->fpu);
#ifdef ELF_CORE_COPY_XFPREGS
	kfree(info->xfpu);
#endif
}

static struct vm_area_struct *first_vma(struct task_struct *tsk,
					struct vm_area_struct *gate_vma)
{
	struct vm_area_struct *ret = tsk->mm->mmap;

	if (ret)
		return ret;
	return gate_vma;
}
/*
 * Helper function for iterating across a vma list.  It ensures that the caller
 * will visit `gate_vma' prior to terminating the search.
 */
static struct vm_area_struct *next_vma(struct vm_area_struct *this_vma,
					struct vm_area_struct *gate_vma)
{
	struct vm_area_struct *ret;

	ret = this_vma->vm_next;
	if (ret)
		return ret;
	if (this_vma == gate_vma)
		return NULL;
	return gate_vma;
}

static void fill_extnum_info(struct elfhdr *elf, struct elf_shdr *shdr4extnum,
			     elf_addr_t e_shoff, int segs)
{
	elf->e_shoff = e_shoff;
	elf->e_shentsize = sizeof(*shdr4extnum);
	elf->e_shnum = 1;
	elf->e_shstrndx = SHN_UNDEF;

	memset(shdr4extnum, 0, sizeof(*shdr4extnum));

	shdr4extnum->sh_type = SHT_NULL;
	shdr4extnum->sh_size = elf->e_shnum;
	shdr4extnum->sh_link = elf->e_shstrndx;
	shdr4extnum->sh_info = segs;
}

Linus Torvalds's avatar
Linus Torvalds committed
/*
 * 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(struct coredump_params *cprm)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int has_dumped = 0;
	mm_segment_t fs;
	int segs, i;
	size_t vma_data_size = 0;
	struct vm_area_struct *vma, *gate_vma;
Linus Torvalds's avatar
Linus Torvalds committed
	struct elfhdr *elf = NULL;
	loff_t offset = 0, dataoff;
	struct elf_shdr *shdr4extnum = NULL;
	Elf_Half e_phnum;
	elf_addr_t e_shoff;
Linus Torvalds's avatar
Linus Torvalds committed

	/*
	 * We no longer stop all VM operations.
	 * 
	 * This is because those proceses that could possibly change map_count
	 * or the mmap / vma pages are now blocked in do_exit on current
	 * finishing this core dump.
Linus Torvalds's avatar
Linus Torvalds committed
	 *
	 * Only ptrace can touch these memory addresses, but it doesn't change
	 * the map_count or the pages allocated. So no possibility of crashing
Linus Torvalds's avatar
Linus Torvalds committed
	 * exists while dumping the mm->vm_next areas to the core file.
	 */
  
	/* alloc memory for large data structures: too large to be on stack */
	elf = kmalloc(sizeof(*elf), GFP_KERNEL);
	if (!elf)
	/*
	 * The number of segs are recored into ELF header as 16bit value.
	 * Please check DEFAULT_MAX_MAP_COUNT definition when you modify here.
	 */
Linus Torvalds's avatar
Linus Torvalds committed
	segs = current->mm->map_count;
	segs += elf_core_extra_phdrs();
Linus Torvalds's avatar
Linus Torvalds committed

	gate_vma = get_gate_vma(current->mm);
	if (gate_vma != NULL)
		segs++;

	/* for notes section */
	segs++;

	/* If segs > PN_XNUM(0xffff), then e_phnum overflows. To avoid
	 * this, kernel supports extended numbering. Have a look at
	 * include/linux/elf.h for further information. */
	e_phnum = segs > PN_XNUM ? PN_XNUM : segs;

Linus Torvalds's avatar
Linus Torvalds committed
	/*
	 * Collect all the non-memory information about the process for the
	 * notes.  This also sets up the file header.
Linus Torvalds's avatar
Linus Torvalds committed
	 */
	if (!fill_note_info(elf, e_phnum, &info, cprm->siginfo, cprm->regs))
		goto cleanup;
Linus Torvalds's avatar
Linus Torvalds committed

	has_dumped = 1;
Linus Torvalds's avatar
Linus Torvalds committed
	fs = get_fs();
	set_fs(KERNEL_DS);

	offset += sizeof(*elf);				/* Elf header */
	offset += segs * sizeof(struct elf_phdr);	/* Program headers */
Linus Torvalds's avatar
Linus Torvalds committed

	/* Write notes phdr entry */
	{
		size_t sz = get_note_info_size(&info);
Linus Torvalds's avatar
Linus Torvalds committed

		sz += elf_coredump_extra_notes_size();
		phdr4note = kmalloc(sizeof(*phdr4note), GFP_KERNEL);
		if (!phdr4note)

		fill_elf_note_phdr(phdr4note, sz, offset);
		offset += sz;
Linus Torvalds's avatar
Linus Torvalds committed
	}

	dataoff = offset = roundup(offset, ELF_EXEC_PAGESIZE);

	vma_filesz = kmalloc_array(segs - 1, sizeof(*vma_filesz), GFP_KERNEL);
	if (!vma_filesz)
		goto end_coredump;

	for (i = 0, vma = first_vma(current, gate_vma); vma != NULL;
			vma = next_vma(vma, gate_vma)) {
		unsigned long dump_size;

		dump_size = vma_dump_size(vma, cprm->mm_flags);
		vma_filesz[i++] = dump_size;
		vma_data_size += dump_size;
	}

	offset += vma_data_size;
	offset += elf_core_extra_data_size();
	e_shoff = offset;

	if (e_phnum == PN_XNUM) {
		shdr4extnum = kmalloc(sizeof(*shdr4extnum), GFP_KERNEL);
		if (!shdr4extnum)
			goto end_coredump;
		fill_extnum_info(elf, shdr4extnum, e_shoff, segs);
	}

	offset = dataoff;

Al Viro's avatar
Al Viro committed
	if (!dump_emit(cprm, elf, sizeof(*elf)))
Al Viro's avatar
Al Viro committed
	if (!dump_emit(cprm, phdr4note, sizeof(*phdr4note)))
Linus Torvalds's avatar
Linus Torvalds committed
	/* Write program headers for segments dump */
	for (i = 0, vma = first_vma(current, gate_vma); vma != NULL;
			vma = next_vma(vma, gate_vma)) {
Linus Torvalds's avatar
Linus Torvalds committed
		struct elf_phdr phdr;

		phdr.p_type = PT_LOAD;
		phdr.p_offset = offset;
		phdr.p_vaddr = vma->vm_start;
		phdr.p_paddr = 0;
Roland McGrath's avatar
Roland McGrath committed
		phdr.p_memsz = vma->vm_end - vma->vm_start;
Linus Torvalds's avatar
Linus Torvalds committed
		offset += phdr.p_filesz;
		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;
Linus Torvalds's avatar
Linus Torvalds committed
		phdr.p_align = ELF_EXEC_PAGESIZE;

Al Viro's avatar
Al Viro committed
		if (!dump_emit(cprm, &phdr, sizeof(phdr)))
	if (!elf_core_write_extra_phdrs(cprm, offset))
Linus Torvalds's avatar
Linus Torvalds committed

 	/* write out the notes section */
Al Viro's avatar
Al Viro committed
	if (!write_note_info(&info, cprm))
		goto end_coredump;
Linus Torvalds's avatar
Linus Torvalds committed

	if (elf_coredump_extra_notes_write(cprm))
	if (!dump_skip(cprm, dataoff - cprm->pos))
Hugh Dickins's avatar
Hugh Dickins committed
		goto end_coredump;
Linus Torvalds's avatar
Linus Torvalds committed

	for (i = 0, vma = first_vma(current, gate_vma); vma != NULL;
			vma = next_vma(vma, gate_vma)) {
Linus Torvalds's avatar
Linus Torvalds committed
		unsigned long addr;
Roland McGrath's avatar
Roland McGrath committed
		unsigned long end;
Linus Torvalds's avatar
Linus Torvalds committed

		end = vma->vm_start + vma_filesz[i++];
Linus Torvalds's avatar
Linus Torvalds committed

Roland McGrath's avatar
Roland McGrath committed
		for (addr = vma->vm_start; addr < end; addr += PAGE_SIZE) {
Hugh Dickins's avatar
Hugh Dickins committed
			int stop;

			page = get_dump_page(addr);
			if (page) {
				void *kaddr = kmap(page);
				stop = !dump_emit(cprm, kaddr, PAGE_SIZE);
Hugh Dickins's avatar
Hugh Dickins committed
				kunmap(page);
Hugh Dickins's avatar
Hugh Dickins committed
			} else
				stop = !dump_skip(cprm, PAGE_SIZE);
Hugh Dickins's avatar
Hugh Dickins committed
			if (stop)
				goto end_coredump;
	if (!elf_core_write_extra_data(cprm))
Linus Torvalds's avatar
Linus Torvalds committed

	if (e_phnum == PN_XNUM) {
		if (!dump_emit(cprm, shdr4extnum, sizeof(*shdr4extnum)))
Linus Torvalds's avatar
Linus Torvalds committed
end_coredump:
	set_fs(fs);

cleanup:
	free_note_info(&info);
	kfree(shdr4extnum);
Linus Torvalds's avatar
Linus Torvalds committed
	return has_dumped;
}

#endif		/* CONFIG_ELF_CORE */
Linus Torvalds's avatar
Linus Torvalds committed

static int __init init_elf_binfmt(void)
{
Al Viro's avatar
Al Viro committed
	register_binfmt(&elf_format);
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
}

static void __exit exit_elf_binfmt(void)
{
	/* Remove the COFF and ELF loaders. */
	unregister_binfmt(&elf_format);
}

core_initcall(init_elf_binfmt);
module_exit(exit_elf_binfmt);
MODULE_LICENSE("GPL");