// SPDX-License-Identifier: GPL-2.0-only /* * fs/proc/vmcore.c Interface for accessing the crash * dump from the system's previous life. * Heavily borrowed from fs/proc/kcore.c * Created by: Hariprasad Nellitheertha ([email protected]) * Copyright (C) IBM Corporation, 2004. All rights reserved * */ #include <linux/mm.h> #include <linux/kcore.h> #include <linux/user.h> #include <linux/elf.h> #include <linux/elfcore.h> #include <linux/export.h> #include <linux/slab.h> #include <linux/highmem.h> #include <linux/printk.h> #include <linux/memblock.h> #include <linux/init.h> #include <linux/crash_dump.h> #include <linux/list.h> #include <linux/moduleparam.h> #include <linux/mutex.h> #include <linux/vmalloc.h> #include <linux/pagemap.h> #include <linux/uio.h> #include <linux/cc_platform.h> #include <asm/io.h> #include "internal.h" /* List representing chunks of contiguous memory areas and their offsets in * vmcore file. */ static LIST_HEAD(vmcore_list); /* Stores the pointer to the buffer containing kernel elf core headers. */ static char *elfcorebuf; static size_t elfcorebuf_sz; static size_t elfcorebuf_sz_orig; static char *elfnotes_buf; static size_t elfnotes_sz; /* Size of all notes minus the device dump notes */ static size_t elfnotes_orig_sz; /* Total size of vmcore file. */ static u64 vmcore_size; static struct proc_dir_entry *proc_vmcore; #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP /* Device Dump list and mutex to synchronize access to list */ static LIST_HEAD(vmcoredd_list); static DEFINE_MUTEX(vmcoredd_mutex); static bool vmcoredd_disabled; core_param(…); #endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ /* Device Dump Size */ static size_t vmcoredd_orig_sz; static DEFINE_SPINLOCK(vmcore_cb_lock); DEFINE_STATIC_SRCU(…); /* List of registered vmcore callbacks. */ static LIST_HEAD(vmcore_cb_list); /* Whether the vmcore has been opened once. */ static bool vmcore_opened; void register_vmcore_cb(struct vmcore_cb *cb) { … } EXPORT_SYMBOL_GPL(…); void unregister_vmcore_cb(struct vmcore_cb *cb) { … } EXPORT_SYMBOL_GPL(…); static bool pfn_is_ram(unsigned long pfn) { … } static int open_vmcore(struct inode *inode, struct file *file) { … } /* Reads a page from the oldmem device from given offset. */ ssize_t read_from_oldmem(struct iov_iter *iter, size_t count, u64 *ppos, bool encrypted) { … } /* * Architectures may override this function to allocate ELF header in 2nd kernel */ int __weak elfcorehdr_alloc(unsigned long long *addr, unsigned long long *size) { … } /* * Architectures may override this function to free header */ void __weak elfcorehdr_free(unsigned long long addr) { … } /* * Architectures may override this function to read from ELF header */ ssize_t __weak elfcorehdr_read(char *buf, size_t count, u64 *ppos) { … } /* * Architectures may override this function to read from notes sections */ ssize_t __weak elfcorehdr_read_notes(char *buf, size_t count, u64 *ppos) { … } /* * Architectures may override this function to map oldmem */ int __weak remap_oldmem_pfn_range(struct vm_area_struct *vma, unsigned long from, unsigned long pfn, unsigned long size, pgprot_t prot) { … } /* * Architectures which support memory encryption override this. */ ssize_t __weak copy_oldmem_page_encrypted(struct iov_iter *iter, unsigned long pfn, size_t csize, unsigned long offset) { … } #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP static int vmcoredd_copy_dumps(struct iov_iter *iter, u64 start, size_t size) { … } #ifdef CONFIG_MMU static int vmcoredd_mmap_dumps(struct vm_area_struct *vma, unsigned long dst, u64 start, size_t size) { … } #endif /* CONFIG_MMU */ #endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ /* Read from the ELF header and then the crash dump. On error, negative value is * returned otherwise number of bytes read are returned. */ static ssize_t __read_vmcore(struct iov_iter *iter, loff_t *fpos) { … } static ssize_t read_vmcore(struct kiocb *iocb, struct iov_iter *iter) { … } /* * The vmcore fault handler uses the page cache and fills data using the * standard __read_vmcore() function. * * On s390 the fault handler is used for memory regions that can't be mapped * directly with remap_pfn_range(). */ static vm_fault_t mmap_vmcore_fault(struct vm_fault *vmf) { … } static const struct vm_operations_struct vmcore_mmap_ops = …; /** * vmcore_alloc_buf - allocate buffer in vmalloc memory * @size: size of buffer * * If CONFIG_MMU is defined, use vmalloc_user() to allow users to mmap * the buffer to user-space by means of remap_vmalloc_range(). * * If CONFIG_MMU is not defined, use vzalloc() since mmap_vmcore() is * disabled and there's no need to allow users to mmap the buffer. */ static inline char *vmcore_alloc_buf(size_t size) { … } /* * Disable mmap_vmcore() if CONFIG_MMU is not defined. MMU is * essential for mmap_vmcore() in order to map physically * non-contiguous objects (ELF header, ELF note segment and memory * regions in the 1st kernel pointed to by PT_LOAD entries) into * virtually contiguous user-space in ELF layout. */ #ifdef CONFIG_MMU /* * remap_oldmem_pfn_checked - do remap_oldmem_pfn_range replacing all pages * reported as not being ram with the zero page. * * @vma: vm_area_struct describing requested mapping * @from: start remapping from * @pfn: page frame number to start remapping to * @size: remapping size * @prot: protection bits * * Returns zero on success, -EAGAIN on failure. */ static int remap_oldmem_pfn_checked(struct vm_area_struct *vma, unsigned long from, unsigned long pfn, unsigned long size, pgprot_t prot) { … } static int vmcore_remap_oldmem_pfn(struct vm_area_struct *vma, unsigned long from, unsigned long pfn, unsigned long size, pgprot_t prot) { … } static int mmap_vmcore(struct file *file, struct vm_area_struct *vma) { … } #else static int mmap_vmcore(struct file *file, struct vm_area_struct *vma) { return -ENOSYS; } #endif static const struct proc_ops vmcore_proc_ops = …; static struct vmcore* __init get_new_element(void) { … } static u64 get_vmcore_size(size_t elfsz, size_t elfnotesegsz, struct list_head *vc_list) { … } /** * update_note_header_size_elf64 - update p_memsz member of each PT_NOTE entry * * @ehdr_ptr: ELF header * * This function updates p_memsz member of each PT_NOTE entry in the * program header table pointed to by @ehdr_ptr to real size of ELF * note segment. */ static int __init update_note_header_size_elf64(const Elf64_Ehdr *ehdr_ptr) { … } /** * get_note_number_and_size_elf64 - get the number of PT_NOTE program * headers and sum of real size of their ELF note segment headers and * data. * * @ehdr_ptr: ELF header * @nr_ptnote: buffer for the number of PT_NOTE program headers * @sz_ptnote: buffer for size of unique PT_NOTE program header * * This function is used to merge multiple PT_NOTE program headers * into a unique single one. The resulting unique entry will have * @sz_ptnote in its phdr->p_mem. * * It is assumed that program headers with PT_NOTE type pointed to by * @ehdr_ptr has already been updated by update_note_header_size_elf64 * and each of PT_NOTE program headers has actual ELF note segment * size in its p_memsz member. */ static int __init get_note_number_and_size_elf64(const Elf64_Ehdr *ehdr_ptr, int *nr_ptnote, u64 *sz_ptnote) { … } /** * copy_notes_elf64 - copy ELF note segments in a given buffer * * @ehdr_ptr: ELF header * @notes_buf: buffer into which ELF note segments are copied * * This function is used to copy ELF note segment in the 1st kernel * into the buffer @notes_buf in the 2nd kernel. It is assumed that * size of the buffer @notes_buf is equal to or larger than sum of the * real ELF note segment headers and data. * * It is assumed that program headers with PT_NOTE type pointed to by * @ehdr_ptr has already been updated by update_note_header_size_elf64 * and each of PT_NOTE program headers has actual ELF note segment * size in its p_memsz member. */ static int __init copy_notes_elf64(const Elf64_Ehdr *ehdr_ptr, char *notes_buf) { … } /* Merges all the PT_NOTE headers into one. */ static int __init merge_note_headers_elf64(char *elfptr, size_t *elfsz, char **notes_buf, size_t *notes_sz) { … } /** * update_note_header_size_elf32 - update p_memsz member of each PT_NOTE entry * * @ehdr_ptr: ELF header * * This function updates p_memsz member of each PT_NOTE entry in the * program header table pointed to by @ehdr_ptr to real size of ELF * note segment. */ static int __init update_note_header_size_elf32(const Elf32_Ehdr *ehdr_ptr) { … } /** * get_note_number_and_size_elf32 - get the number of PT_NOTE program * headers and sum of real size of their ELF note segment headers and * data. * * @ehdr_ptr: ELF header * @nr_ptnote: buffer for the number of PT_NOTE program headers * @sz_ptnote: buffer for size of unique PT_NOTE program header * * This function is used to merge multiple PT_NOTE program headers * into a unique single one. The resulting unique entry will have * @sz_ptnote in its phdr->p_mem. * * It is assumed that program headers with PT_NOTE type pointed to by * @ehdr_ptr has already been updated by update_note_header_size_elf32 * and each of PT_NOTE program headers has actual ELF note segment * size in its p_memsz member. */ static int __init get_note_number_and_size_elf32(const Elf32_Ehdr *ehdr_ptr, int *nr_ptnote, u64 *sz_ptnote) { … } /** * copy_notes_elf32 - copy ELF note segments in a given buffer * * @ehdr_ptr: ELF header * @notes_buf: buffer into which ELF note segments are copied * * This function is used to copy ELF note segment in the 1st kernel * into the buffer @notes_buf in the 2nd kernel. It is assumed that * size of the buffer @notes_buf is equal to or larger than sum of the * real ELF note segment headers and data. * * It is assumed that program headers with PT_NOTE type pointed to by * @ehdr_ptr has already been updated by update_note_header_size_elf32 * and each of PT_NOTE program headers has actual ELF note segment * size in its p_memsz member. */ static int __init copy_notes_elf32(const Elf32_Ehdr *ehdr_ptr, char *notes_buf) { … } /* Merges all the PT_NOTE headers into one. */ static int __init merge_note_headers_elf32(char *elfptr, size_t *elfsz, char **notes_buf, size_t *notes_sz) { … } /* Add memory chunks represented by program headers to vmcore list. Also update * the new offset fields of exported program headers. */ static int __init process_ptload_program_headers_elf64(char *elfptr, size_t elfsz, size_t elfnotes_sz, struct list_head *vc_list) { … } static int __init process_ptload_program_headers_elf32(char *elfptr, size_t elfsz, size_t elfnotes_sz, struct list_head *vc_list) { … } /* Sets offset fields of vmcore elements. */ static void set_vmcore_list_offsets(size_t elfsz, size_t elfnotes_sz, struct list_head *vc_list) { … } static void free_elfcorebuf(void) { … } static int __init parse_crash_elf64_headers(void) { … } static int __init parse_crash_elf32_headers(void) { … } static int __init parse_crash_elf_headers(void) { … } #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP /** * vmcoredd_write_header - Write vmcore device dump header at the * beginning of the dump's buffer. * @buf: Output buffer where the note is written * @data: Dump info * @size: Size of the dump * * Fills beginning of the dump's buffer with vmcore device dump header. */ static void vmcoredd_write_header(void *buf, struct vmcoredd_data *data, u32 size) { … } /** * vmcoredd_update_program_headers - Update all ELF program headers * @elfptr: Pointer to elf header * @elfnotesz: Size of elf notes aligned to page size * @vmcoreddsz: Size of device dumps to be added to elf note header * * Determine type of ELF header (Elf64 or Elf32) and update the elf note size. * Also update the offsets of all the program headers after the elf note header. */ static void vmcoredd_update_program_headers(char *elfptr, size_t elfnotesz, size_t vmcoreddsz) { … } /** * vmcoredd_update_size - Update the total size of the device dumps and update * ELF header * @dump_size: Size of the current device dump to be added to total size * * Update the total size of all the device dumps and update the ELF program * headers. Calculate the new offsets for the vmcore list and update the * total vmcore size. */ static void vmcoredd_update_size(size_t dump_size) { … } /** * vmcore_add_device_dump - Add a buffer containing device dump to vmcore * @data: dump info. * * Allocate a buffer and invoke the calling driver's dump collect routine. * Write ELF note at the beginning of the buffer to indicate vmcore device * dump and add the dump to global list. */ int vmcore_add_device_dump(struct vmcoredd_data *data) { … } EXPORT_SYMBOL(…); #endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ /* Free all dumps in vmcore device dump list */ static void vmcore_free_device_dumps(void) { … } /* Init function for vmcore module. */ static int __init vmcore_init(void) { … } fs_initcall(vmcore_init); /* Cleanup function for vmcore module. */ void vmcore_cleanup(void) { … }