// 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 (hari@in.ibm.com) * Copyright (C) IBM Corporation, 2004. All rights reserved *
*/
DEFINE_STATIC_SRCU(vmcore_cb_srcu); /* List of registered vmcore callbacks. */ static LIST_HEAD(vmcore_cb_list); /* Whether the vmcore has been opened once. */ staticbool vmcore_opened; /* Whether the vmcore is currently open. */ staticunsignedint vmcore_open;
void register_vmcore_cb(struct vmcore_cb *cb)
{
INIT_LIST_HEAD(&cb->next);
mutex_lock(&vmcore_mutex);
list_add_tail(&cb->next, &vmcore_cb_list); /* * Registering a vmcore callback after the vmcore was opened is * very unusual (e.g., manual driver loading).
*/ if (vmcore_opened)
pr_warn_once("Unexpected vmcore callback registration\n"); if (!vmcore_open && cb->get_device_ram)
vmcore_process_device_ram(cb);
mutex_unlock(&vmcore_mutex);
}
EXPORT_SYMBOL_GPL(register_vmcore_cb);
void unregister_vmcore_cb(struct vmcore_cb *cb)
{
mutex_lock(&vmcore_mutex);
list_del_rcu(&cb->next); /* * Unregistering a vmcore callback after the vmcore was opened is * very unusual (e.g., forced driver removal), but we cannot stop * unregistering.
*/ if (vmcore_opened)
pr_warn_once("Unexpected vmcore callback unregistration\n");
mutex_unlock(&vmcore_mutex);
/* * Architectures may override this function to allocate ELF header in 2nd kernel
*/ int __weak elfcorehdr_alloc(unsignedlonglong *addr, unsignedlonglong *size)
{ return 0;
}
/* * Architectures may override this function to free header
*/ void __weak elfcorehdr_free(unsignedlonglong addr)
{}
/* * Architectures may override this function to read from ELF header
*/
ssize_t __weak elfcorehdr_read(char *buf, size_t count, u64 *ppos)
{ struct kvec kvec = { .iov_base = buf, .iov_len = count }; struct iov_iter iter;
/* 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)
{ struct vmcore_range *m = NULL;
ssize_t acc = 0, tmp;
size_t tsz;
u64 start;
if (!iov_iter_count(iter) || *fpos >= vmcore_size) return 0;
/* We add device dumps before other elf notes because the * other elf notes may not fill the elf notes buffer * completely and we will end up with zero-filled data * between the elf notes and the device dumps. Tools will * then try to decode this zero-filled data as valid notes * and we don't want that. Hence, adding device dumps before * the other elf notes ensure that zero-filled data can be * avoided.
*/ #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP /* Read device dumps */ if (*fpos < elfcorebuf_sz + vmcoredd_orig_sz) {
tsz = min(elfcorebuf_sz + vmcoredd_orig_sz -
(size_t)*fpos, iov_iter_count(iter));
start = *fpos - elfcorebuf_sz; if (vmcoredd_copy_dumps(iter, start, tsz)) return -EFAULT;
*fpos += tsz;
acc += tsz;
/* leave now if filled buffer already */ if (!iov_iter_count(iter)) return acc;
} #endif/* CONFIG_PROC_VMCORE_DEVICE_DUMP */
/** * 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.
*/ staticinlinechar *vmcore_alloc_buf(size_t size)
{ #ifdef CONFIG_MMU return vmalloc_user(size); #else return vzalloc(size); #endif
}
/* * 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
/* * 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)
{ #ifdef CONFIG_S390 struct address_space *mapping = vmf->vma->vm_file->f_mapping;
pgoff_t index = vmf->pgoff; struct iov_iter iter; struct kvec kvec; struct page *page;
loff_t offset; int rc;
page = find_or_create_page(mapping, index, GFP_KERNEL); if (!page) return VM_FAULT_OOM; if (!PageUptodate(page)) {
offset = (loff_t) index << PAGE_SHIFT;
kvec.iov_base = page_address(page);
kvec.iov_len = PAGE_SIZE;
iov_iter_kvec(&iter, ITER_DEST, &kvec, 1, PAGE_SIZE);
for (pos = pos_start; pos < pos_end; ++pos) { if (!pfn_is_ram(pos)) { /* * We hit a page which is not ram. Remap the continuous * region between pos_start and pos-1 and replace * the non-ram page at pos with the zero page.
*/ if (pos > pos_start) { /* Remap continuous region */
map_size = (pos - pos_start) << PAGE_SHIFT; if (remap_oldmem_pfn_range(vma, from + len,
pos_start, map_size,
prot)) goto fail;
len += map_size;
} /* Remap the zero page */ if (remap_oldmem_pfn_range(vma, from + len,
zeropage_pfn,
PAGE_SIZE, prot)) goto fail;
len += PAGE_SIZE;
pos_start = pos + 1;
}
} if (pos > pos_start) { /* Remap the rest */
map_size = (pos - pos_start) << PAGE_SHIFT; if (remap_oldmem_pfn_range(vma, from + len, pos_start,
map_size, prot)) goto fail;
} return 0;
fail:
do_munmap(vma->vm_mm, from, len, NULL); return -EAGAIN;
}
/* * Check if a callback was registered to avoid looping over all * pages without a reason.
*/
idx = srcu_read_lock(&vmcore_cb_srcu); if (!list_empty(&vmcore_cb_list))
ret = remap_oldmem_pfn_checked(vma, from, pfn, size, prot); else
ret = remap_oldmem_pfn_range(vma, from, pfn, size, prot);
srcu_read_unlock(&vmcore_cb_srcu, idx); return ret;
}
if (start < elfcorebuf_sz + elfnotes_sz) { void *kaddr;
/* We add device dumps before other elf notes because the * other elf notes may not fill the elf notes buffer * completely and we will end up with zero-filled data * between the elf notes and the device dumps. Tools will * then try to decode this zero-filled data as valid notes * and we don't want that. Hence, adding device dumps before * the other elf notes ensure that zero-filled data can be * avoided. This also ensures that the device dumps and * other elf notes can be properly mmaped at page aligned * address.
*/ #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP /* Read device dumps */ if (start < elfcorebuf_sz + vmcoredd_orig_sz) {
u64 start_off;
/** * 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.
*/ staticint __init update_note_header_size_elf64(const Elf64_Ehdr *ehdr_ptr)
{ int i, rc=0;
Elf64_Phdr *phdr_ptr;
Elf64_Nhdr *nhdr_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.
*/ staticint __init get_note_number_and_size_elf64(const Elf64_Ehdr *ehdr_ptr, int *nr_ptnote, u64 *sz_ptnote)
{ int i;
Elf64_Phdr *phdr_ptr;
*nr_ptnote = *sz_ptnote = 0;
phdr_ptr = (Elf64_Phdr *)(ehdr_ptr + 1); for (i = 0; i < ehdr_ptr->e_phnum; i++, phdr_ptr++) { if (phdr_ptr->p_type != PT_NOTE) continue;
*nr_ptnote += 1;
*sz_ptnote += phdr_ptr->p_memsz;
}
return 0;
}
/** * 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.
*/ staticint __init copy_notes_elf64(const Elf64_Ehdr *ehdr_ptr, char *notes_buf)
{ int i, rc=0;
Elf64_Phdr *phdr_ptr;
phdr_ptr = (Elf64_Phdr*)(ehdr_ptr + 1);
for (i = 0; i < ehdr_ptr->e_phnum; i++, phdr_ptr++) {
u64 offset; if (phdr_ptr->p_type != PT_NOTE) continue;
offset = phdr_ptr->p_offset;
rc = elfcorehdr_read_notes(notes_buf, phdr_ptr->p_memsz,
&offset); if (rc < 0) return rc;
notes_buf += phdr_ptr->p_memsz;
}
return 0;
}
/* Merges all the PT_NOTE headers into one. */ staticint __init merge_note_headers_elf64(char *elfptr, size_t *elfsz, char **notes_buf, size_t *notes_sz)
{ int i, nr_ptnote=0, rc=0; char *tmp;
Elf64_Ehdr *ehdr_ptr;
Elf64_Phdr phdr;
u64 phdr_sz = 0, note_off;
ehdr_ptr = (Elf64_Ehdr *)elfptr;
rc = update_note_header_size_elf64(ehdr_ptr); if (rc < 0) return rc;
/* Store the size of all notes. We need this to update the note * header when the device dumps will be added.
*/
elfnotes_orig_sz = phdr.p_memsz;
return 0;
}
/** * 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.
*/ staticint __init update_note_header_size_elf32(const Elf32_Ehdr *ehdr_ptr)
{ int i, rc=0;
Elf32_Phdr *phdr_ptr;
Elf32_Nhdr *nhdr_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.
*/ staticint __init get_note_number_and_size_elf32(const Elf32_Ehdr *ehdr_ptr, int *nr_ptnote, u64 *sz_ptnote)
{ int i;
Elf32_Phdr *phdr_ptr;
*nr_ptnote = *sz_ptnote = 0;
phdr_ptr = (Elf32_Phdr *)(ehdr_ptr + 1); for (i = 0; i < ehdr_ptr->e_phnum; i++, phdr_ptr++) { if (phdr_ptr->p_type != PT_NOTE) continue;
*nr_ptnote += 1;
*sz_ptnote += phdr_ptr->p_memsz;
}
return 0;
}
/** * 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.
*/ staticint __init copy_notes_elf32(const Elf32_Ehdr *ehdr_ptr, char *notes_buf)
{ int i, rc=0;
Elf32_Phdr *phdr_ptr;
phdr_ptr = (Elf32_Phdr*)(ehdr_ptr + 1);
for (i = 0; i < ehdr_ptr->e_phnum; i++, phdr_ptr++) {
u64 offset; if (phdr_ptr->p_type != PT_NOTE) continue;
offset = phdr_ptr->p_offset;
rc = elfcorehdr_read_notes(notes_buf, phdr_ptr->p_memsz,
&offset); if (rc < 0) return rc;
notes_buf += phdr_ptr->p_memsz;
}
return 0;
}
/* Merges all the PT_NOTE headers into one. */ staticint __init merge_note_headers_elf32(char *elfptr, size_t *elfsz, char **notes_buf, size_t *notes_sz)
{ int i, nr_ptnote=0, rc=0; char *tmp;
Elf32_Ehdr *ehdr_ptr;
Elf32_Phdr phdr;
u64 phdr_sz = 0, note_off;
ehdr_ptr = (Elf32_Ehdr *)elfptr;
rc = update_note_header_size_elf32(ehdr_ptr); if (rc < 0) return rc;
/* Store the size of all notes. We need this to update the note * header when the device dumps will be added.
*/
elfnotes_orig_sz = phdr.p_memsz;
return 0;
}
/* Add memory chunks represented by program headers to vmcore list. Also update
* the new offset fields of exported program headers. */ staticint __init process_ptload_program_headers_elf64(char *elfptr,
size_t elfsz,
size_t elfnotes_sz, struct list_head *vc_list)
{ int i;
Elf64_Ehdr *ehdr_ptr;
Elf64_Phdr *phdr_ptr;
loff_t vmcore_off;
#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.
*/ staticvoid vmcoredd_write_header(void *buf, struct vmcoredd_data *data,
u32 size)
{ struct vmcoredd_header *vdd_hdr = (struct vmcoredd_header *)buf;
/** * 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.
*/ staticvoid vmcoredd_update_program_headers(char *elfptr, size_t elfnotesz,
size_t vmcoreddsz)
{ unsignedchar *e_ident = (unsignedchar *)elfptr;
u64 start, end, size;
loff_t vmcore_off;
u32 i;
/** * 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.
*/ staticvoid vmcoredd_update_size(size_t dump_size)
{
vmcoredd_orig_sz += dump_size;
elfnotes_sz = roundup(elfnotes_orig_sz, PAGE_SIZE) + vmcoredd_orig_sz;
vmcoredd_update_program_headers(elfcorebuf, elfnotes_sz,
vmcoredd_orig_sz);
/* Update vmcore list offsets */
set_vmcore_list_offsets(elfcorebuf_sz, elfnotes_sz, &vmcore_list);
/** * 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)
{ struct vmcoredd_node *dump; void *buf = NULL;
size_t data_size; int ret;
if (vmcoredd_disabled) {
pr_err_once("Device dump is disabled\n"); return -EINVAL;
}
if (!data || !strlen(data->dump_name) ||
!data->vmcoredd_callback || !data->size) return -EINVAL;
dump = vzalloc(sizeof(*dump)); if (!dump) return -ENOMEM;
/* Keep size of the buffer page aligned so that it can be mmaped */
data_size = roundup(sizeof(struct vmcoredd_header) + data->size,
PAGE_SIZE);
/* Allocate buffer for driver's to write their dumps */
buf = vmcore_alloc_buf(data_size); if (!buf) {
ret = -ENOMEM; goto out_err;
}
/* Invoke the driver's dump collection routing */
ret = data->vmcoredd_callback(data, buf + sizeof(struct vmcoredd_header)); if (ret) goto out_err;
dump->buf = buf;
dump->size = data_size;
/* Add the dump to driver sysfs list and update the elfcore hdr */
scoped_guard(mutex, &vmcore_mutex) { if (vmcore_opened)
pr_warn_once("Unexpected adding of device dump\n"); if (vmcore_open) {
ret = -EBUSY; goto out_err;
}
for (i = 0, phdr = phdr_start; i < ehdr->e_phnum; i++, phdr++) {
u64 start, end;
/* * After merge_note_headers_elf64() we should only have a single * PT_NOTE entry that starts immediately after elfcorebuf_sz.
*/ if (phdr->p_type == PT_NOTE) {
phdr->p_offset = elfcorebuf_sz; continue;
}
/* * Make sure we have sufficient space to include the new PT_LOAD * entries.
*/
rc = vmcore_realloc_elfcore_buffer_elf64(new_size); if (rc) {
pr_err("resizing elfcore failed\n"); return rc;
}
/* Modify our used elfcore buffer size to cover the new entries. */
elfcorebuf_sz = new_size;
/* We only support Elf64 dumps for now. */ if (WARN_ON_ONCE(e_ident[EI_CLASS] != ELFCLASS64)) {
pr_err("device ram ranges only support Elf64\n"); return;
}
if (cb->get_device_ram(cb, &list)) {
pr_err("obtaining device ram ranges failed\n"); return;
}
count = list_count_nodes(&list); if (!count) return;
/* * For some reason these ranges are already know? Might happen * with unusual register->unregister->register sequences; we'll simply * sanity check using the first range.
*/
first = list_first_entry(&list, struct vmcore_range, list);
list_for_each_entry(m, &vmcore_list, list) { unsignedlonglong m_end = m->paddr + m->size; unsignedlonglong first_end = first->paddr + first->size;
/* If adding the mem nodes succeeds, they must not be freed. */ if (!vmcore_add_device_ram_elf64(&list, count)) return;
out_free:
vmcore_free_ranges(&list);
} #else/* !CONFIG_PROC_VMCORE_DEVICE_RAM */ staticvoid vmcore_process_device_ram(struct vmcore_cb *cb)
{
} #endif/* CONFIG_PROC_VMCORE_DEVICE_RAM */
/* Free all dumps in vmcore device dump list */ staticvoid vmcore_free_device_dumps(void)
{ #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP
mutex_lock(&vmcore_mutex); while (!list_empty(&vmcoredd_list)) { struct vmcoredd_node *dump;
/* Init function for vmcore module. */ staticint __init vmcore_init(void)
{ int rc = 0;
/* Allow architectures to allocate ELF header in 2nd kernel */
rc = elfcorehdr_alloc(&elfcorehdr_addr, &elfcorehdr_size); if (rc) return rc; /* * If elfcorehdr= has been passed in cmdline or created in 2nd kernel, * then capture the dump.
*/ if (!(is_vmcore_usable())) return rc;
rc = parse_crash_elf_headers(); if (rc) {
elfcorehdr_free(elfcorehdr_addr);
pr_warn("not initialized\n"); return rc;
}
elfcorehdr_free(elfcorehdr_addr);
elfcorehdr_addr = ELFCORE_ADDR_ERR;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.