Index: lib/libkvm/kvm.h =================================================================== --- lib/libkvm/kvm.h +++ lib/libkvm/kvm.h @@ -125,6 +125,7 @@ ssize_t kvm_read2(kvm_t *, kvaddr_t, void *, size_t); ssize_t kvm_write(kvm_t *, unsigned long, const void *, size_t); +void *kvm_get_ehdr(kvm_t *); typedef int kvm_walk_pages_cb_t(struct kvm_page *, void *); int kvm_walk_pages(kvm_t *, kvm_walk_pages_cb_t *, void *); __END_DECLS Index: lib/libkvm/kvm.c =================================================================== --- lib/libkvm/kvm.c +++ lib/libkvm/kvm.c @@ -499,3 +499,7 @@ return (kd->arch->ka_walk_pages(kd, cb, closure)); } + +void *kvm_get_ehdr(kvm_t *kd) { + return (void*)(&kd->nlehdr); +} Index: tools/tools/minidump_to_elf/Makefile =================================================================== --- /dev/null +++ tools/tools/minidump_to_elf/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +PROG= minidump-to-elf +SRCS= main.c +MAN= +LIBADD+= kvm +LIBADD+= elf + +.include Index: tools/tools/minidump_to_elf/main.c =================================================================== --- /dev/null +++ tools/tools/minidump_to_elf/main.c @@ -0,0 +1,274 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* A range is a contiguous sets of pages in the minidump. */ +struct range_info { + u_long vaddr; /* Vaddr of the range. */ + u_long off; /* Offset of range in the original core file. */ + u_char prot; /* Protection flags of range. */ + void* buf; /* The data inside the range to write out. */ + size_t rangelen;/* The length of the range. */ + size_t filelen; /* The length of the buf. */ +}; + +struct range_info* ranges; +int ranges_len, ranges_cap; + +char kvm_err[_POSIX2_LINE_MAX]; + +void merge_ranges(void) { + int rin_memory, lin_memory, i, j; + + for (i = 0; i < ranges_len; ++i) { + rin_memory = (ranges[i].off != -1); + for (j = 0; j < ranges_len; ++j) { + lin_memory = (ranges[j].off != -1); + if (i != j && + ranges[j].vaddr + ranges[j].rangelen == ranges[i].vaddr && + (lin_memory || !rin_memory) && + ranges[j].prot == ranges[i].prot) { + ranges[j].rangelen += ranges[i].rangelen; + ranges[j].filelen += ranges[i].filelen; + if (!rin_memory && lin_memory) + ranges[j].off = -1; + ranges[i] = ranges[ranges_len-1]; + ranges_len -= 1; + i -= 1; + break; + } + } + } +} + +int walk_pages_cb(struct kvm_page* page, void* arg) { + int not_in_memory, i; + u_char prot; + + /* NOTE: We don't care about pages that don't have a kernel vaddr. */ + if (page->kmap_vaddr == 0) + return 1; + + prot = 0; + if (page->prot & VM_PROT_READ) prot |= PF_R; + if (page->prot & VM_PROT_WRITE) prot |= PF_W; + if (page->prot & VM_PROT_EXECUTE) prot |= PF_X; + + not_in_memory = (page->offset == -1); + + for (i = ranges_len - 1; i >= 0; --i) { + if (ranges[i].vaddr == page->kmap_vaddr) + errx(EXIT_FAILURE, "Duplicate kernel vaddr found."); + + if (ranges[i].vaddr + ranges[i].rangelen == page->kmap_vaddr && + prot == ranges[i].prot) { + + if (not_in_memory && ranges[i].off + 1 != 0) + continue; + + if (not_in_memory) + ranges[i].off = page->offset; + + ranges[i].rangelen += page->len; + if (ranges[i].off != -1) + ranges[i].filelen += page->len; + + return 1; + } + } + + if (ranges_len == ranges_cap) { + ranges_cap *= 2; + ranges = realloc(ranges, ranges_cap * sizeof(struct range_info)); + } + + ranges[ranges_len].vaddr = page->kmap_vaddr; + ranges[ranges_len].rangelen = page->len; + if (not_in_memory) + ranges[ranges_len].filelen = 0; + else + ranges[ranges_len].filelen = page->len; + ranges[ranges_len].off = page->offset; + ranges[ranges_len].prot = prot; + + ranges_len += 1; + return 1; +} + +void usage(void) { + errx(EXIT_FAILURE, "usage: minidump-to-elf kernel minidump [elf-output]"); +} + +int main(int argc, char** argv) { + kvm_t* kvm; + Elf* e; + GElf_Ehdr *core_header, *ehdr; + GElf_Phdr* phdr; + GElf_Shdr shdr; + Elf_Scn* section; + Elf_Data* data; + const char *kernel, *vmcore, *output_elf; + size_t cr; + int pagesize, elf_class, fd, i; + + if (argc != 3 && argc != 4) + usage(); + + output_elf = "vmcore.elf"; + vmcore = argv[1]; + kernel = argv[2]; + if (argc == 4) + output_elf = argv[3]; + + if ((kvm = kvm_open(kernel, vmcore, NULL, O_RDONLY, kvm_err)) == NULL) + errx(EXIT_FAILURE, "Failed to open vmcore: %s\n", kvm_err); + + ranges_len = 0; + ranges_cap = 128; + ranges = calloc(ranges_cap, sizeof(struct range_info)); + + if (kvm_walk_pages(kvm, walk_pages_cb, NULL) == 0) + errx(EXIT_FAILURE, "kvm_walk_pages() failed"); + + merge_ranges(); + + if (elf_version(EV_CURRENT) == EV_NONE) + errx(EXIT_FAILURE, "Elf library initialization failed: %s", + elf_errmsg(-1)); + + if ((fd = open(output_elf, O_WRONLY | O_CREAT, 0777)) < 0) + errx(EXIT_FAILURE, "Couldn't open 'core-elf' for writing"); + + if ((e = elf_begin(fd, ELF_C_WRITE, NULL)) == NULL) + errx(EXIT_FAILURE, "elf_begin() failed: %s", + elf_errmsg(-1)); + + core_header = (GElf_Ehdr*)kvm_get_ehdr(kvm); + elf_class = core_header->e_ident[EI_CLASS]; + + if ((ehdr = gelf_newehdr(e, elf_class)) == NULL) + errx(EXIT_FAILURE, "gelf_newehdr() failed: %s", + elf_errmsg(-1)); + + ehdr->e_ident[EI_DATA] = core_header->e_ident[EI_DATA]; + ehdr->e_ident[EI_CLASS] = elf_class; + ehdr->e_machine = core_header->e_machine; + ehdr->e_type = ET_CORE; + ehdr->e_ident[EI_OSABI] = core_header->e_ident[EI_OSABI]; + + /* Page size is only different for sparc64 according to man arch. */ + pagesize = 4096; + if (ehdr->e_machine == EM_SPARCV9) + pagesize = 8192; + + if ((phdr = gelf_newphdr(e, ranges_len + 1)) < 0) + errx(EXIT_FAILURE, "gelf_newphdr() failed: %s", + elf_errmsg(-1)); + + /* Put the ranges inside the data segment of sections. */ + for (i = 0; i < ranges_len; ++i) { + ranges[i].buf = malloc(ranges[i].filelen); + cr = kvm_read2(kvm, ranges[i].vaddr, ranges[i].buf, + ranges[i].filelen); + + if (cr != ranges[i].filelen) + errx(EXIT_FAILURE, "kvm_read2() failed"); + + if ((section = elf_newscn(e)) == NULL) + errx(EXIT_FAILURE, "elf_newscn() failed: %s", + elf_errmsg(-1)); + + if ((data = elf_newdata(section)) == NULL) + errx(EXIT_FAILURE, "elf_newdata() failed: %s", + elf_errmsg(-1)); + + /* Fill in the data part of the section */ + data->d_align = pagesize; + data->d_off = 0LL; + data->d_buf= ranges[i].buf; + data->d_type = ELF_T_WORD; + data->d_size = ranges[i].filelen; + data->d_version = EV_CURRENT; + + /* Update the section headers to have data in it. */ + if (&shdr != gelf_getshdr(section, &shdr)) + errx(EXIT_FAILURE, "gelf_getshdr() failed: %s", + elf_errmsg(-1)); + + shdr.sh_name = i; + shdr.sh_type = SHT_PROGBITS; + shdr.sh_flags = SHF_OS_NONCONFORMING; + shdr.sh_entsize = 0; + shdr.sh_addr = ranges[i].vaddr; + if (gelf_update_shdr(section, &shdr) == 0) + errx(EXIT_FAILURE, "gelf_update_shdr() failed: %s", + elf_errmsg(-1)); + } + + /* Update the elf file to update the offsets into the file. */ + if (elf_update(e, ELF_C_NULL) < 0) + errx(EXIT_FAILURE, "elf_update() failed: %s", elf_errmsg(-1)); + + /* + * Refer to the data segments from program headers because that's + * where core files hold this information. + */ + section = NULL; + + for (i = 0; i < ranges_len; ++i) { + if ((section = elf_nextscn(e, section)) == NULL) + errx(EXIT_FAILURE, "Couldn't get all the sections"); + + if (phdr != gelf_getphdr(e, i, phdr)) + errx(EXIT_FAILURE, "gelf_getphdr() failed: %s", + elf_errmsg(-1)); + + if (&shdr != gelf_getshdr(section, &shdr)) + errx(EXIT_FAILURE, "gelf_getshdr() failed: %s", + elf_errmsg(-1)); + + data = elf_getdata(section, NULL); + phdr->p_type = PT_LOAD; + phdr->p_flags = ranges[i].prot; + /* data offset is from where the section starts. */ + phdr->p_offset = shdr.sh_offset + data->d_off; + phdr->p_vaddr = ranges[i].vaddr; + phdr->p_paddr = 0; /* Leave paddr as 0 */ + phdr->p_filesz = ranges[i].filelen; + phdr->p_memsz = ranges[i].rangelen; + phdr->p_align = pagesize; + if (gelf_update_phdr(e, i, phdr) == 0) + errx(EXIT_FAILURE, "gelf_update_phdr() failed: %s", + elf_errmsg(-1)); + } + + /* Program headers header */ + if (phdr != gelf_getphdr(e, ranges_len, phdr)) + errx(EXIT_FAILURE, "gelf_getphdr() failed: %s", + elf_errmsg(-1)); + phdr->p_type = PT_PHDR; + phdr->p_offset = ehdr->e_phoff; + phdr->p_filesz = gelf_fsize(e, ELF_T_PHDR, 1 , EV_CURRENT); + + elf_flagphdr(e, ELF_C_SET, ELF_F_DIRTY); + + if (elf_update(e, ELF_C_WRITE) < 0) + errx(EXIT_FAILURE, "elf_update() failed: %s", elf_errmsg(-1)); + + if (kvm_close(kvm) != 0) + errx(EXIT_FAILURE, "Couldn't close the vmcore \"%s\": %s", + vmcore, kvm_geterr(kvm)); + + for (i = 0; i < ranges_len; ++i) + free(ranges[i].buf); + free(ranges); + elf_end(e); + close(fd); +} + Index: tools/tools/minidump_to_elf/minidump-to-elf.1 =================================================================== --- /dev/null +++ tools/tools/minidump_to_elf/minidump-to-elf.1 @@ -0,0 +1,79 @@ +.\" Copyright 1997 John-Mark Gurney. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' +.\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd February 22, 2019 +.Dt MINIDUMP-TO-ELF 3 +.Os +.Sh NAME +.Nm minidump-to-elf +.Nd convert a kernel minidump into a kernel ELF core file +.Sh SYNOPSIS +.Nm +.Ar kernel +.Ar minidump +.Op Ar elf-output +.Sh DESCRIPTION +The +.Nm +utility takes in as input +.Ar kernel , +and +.Ar minidump +files and produces a kernel core file in ELF format. If the optional +.Ar elf-output +is given then the output will be written to this file. If not, +.Nm +defaults to +.Ar ./vmcore.elf . +.Pp +.Ar kernel +is the kernel binary that has produced the +.Ar minidump . +.Pp +.Ar minidump +is the core dump generated by a kernel panic that is +found by default as +.Ar /var/crash.vmcore.# . +.Sh EXIT STATUS +Exit status is 0 on success, and 1 if the command +fails if a file does not exist, is too short, fails to read properly, +or fails to properly convert the minidump file. +.Sh EXAMPLES +The following is an example of a typical usage +of the +.Nm +command: +.Bd -literal -offset indent +minidump-to-elf kernel.full /var/crash/vmcore.0 vmcore.0.elf +minidump-to-elf kernel.full /var/crash/vmcore.0 +.Ed +.Sh HISTORY +The +.Nm +manual page first appeared in +.Fx 13.0 . +.Sh AUTHORS +This manual page was written by +.An Bora Ozarslan Aq Mt borako.ozarslan@gmail.com .