Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F151070872
D56250.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
14 KB
Referenced Files
None
Subscribers
None
D56250.diff
View Options
diff --git a/lib/libkvm/Makefile b/lib/libkvm/Makefile
--- a/lib/libkvm/Makefile
+++ b/lib/libkvm/Makefile
@@ -13,7 +13,8 @@
kvm_i386.c kvm_minidump_i386.c \
kvm_powerpc.c kvm_powerpc64.c \
kvm_minidump_riscv.c \
- kvm_minidump_powerpc64.c kvm_minidump_powerpc64_hpt.c
+ kvm_minidump_powerpc64.c kvm_minidump_powerpc64_hpt.c \
+ kvm_minidump_to_elf.c
INCS= kvm.h
LIBADD= elf
diff --git a/lib/libkvm/kvm.h b/lib/libkvm/kvm.h
--- a/lib/libkvm/kvm.h
+++ b/lib/libkvm/kvm.h
@@ -34,6 +34,7 @@
#include <sys/types.h>
#include <nlist.h>
+#include <stdbool.h>
/*
* Including vm/vm.h causes namespace pollution issues. For the
@@ -106,6 +107,7 @@
struct kinfo_proc *
kvm_getprocs(kvm_t *, int, int, int *);
int kvm_getswapinfo(kvm_t *, struct kvm_swap *, int, int);
+int kvm_minidump_to_elf(kvm_t *, const char *, bool);
int kvm_native(kvm_t *);
int kvm_nlist(kvm_t *, struct nlist *);
int kvm_nlist2(kvm_t *, struct kvm_nlist *);
diff --git a/lib/libkvm/kvm.3 b/lib/libkvm/kvm.3
--- a/lib/libkvm/kvm.3
+++ b/lib/libkvm/kvm.3
@@ -108,6 +108,9 @@
.Fa resolver
function when opening a descriptor via
.Fn kvm_open2 .
+Use
+.Fn kvm_minidump_to_elf
+to convert a minidump file to a ELF core file for debugger support.
In addition,
the kvm interface defines an integer type
.Pq Vt kvaddr_t
@@ -146,6 +149,7 @@
.Xr kvm_getprocs 3 ,
.Xr kvm_getswapinfo 3 ,
.Xr kvm_kerndisp 3 ,
+.Xr kvm_minidump_to_elf 3 ,
.Xr kvm_native 3 ,
.Xr kvm_nlist 3 ,
.Xr kvm_nlist2 3 ,
diff --git a/lib/libkvm/kvm_minidump_to_elf.3 b/lib/libkvm/kvm_minidump_to_elf.3
new file mode 100644
--- /dev/null
+++ b/lib/libkvm/kvm_minidump_to_elf.3
@@ -0,0 +1,141 @@
+.\"
+.\" Copyright (c) 2026 The FreeBSD Foundation
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Author: Minsoo Choo <minsoochoo0122@proton.me>
+.\"
+.Dd April 4, 2026
+.Dt KVM_MINIDUMP_TO_ELF 3
+.Os
+.Sh NAME
+.Nm kvm_minidump_to_elf
+.Nd convert a kernel minidump to an ELF core file
+.Sh LIBRARY
+.Lb libkvm
+.Sh SYNOPSIS
+.In kvm.h
+.Ft int
+.Fn kvm_minidump_to_elf "kvm_t *kd" "const char *outfile" "bool apply_kerndisp"
+.Sh DESCRIPTION
+The
+.Fn kvm_minidump_to_elf
+function converts the kernel minidump associated with the descriptor
+.Fa kd
+into an ELF core file written to
+.Fa outfile .
+The descriptor must have been opened with
+.Xr kvm_open 3
+or
+.Xr kvm_open2 3
+against a kernel minidump, not a live system.
+.Pp
+The resulting ELF core file contains one
+.Dv PT_LOAD
+program header for each contiguous range of kernel virtual memory
+present in the minidump, plus a
+.Dv PT_PHDR
+entry describing the program header table itself.
+The ELF header fields
+.Pq machine type, data encoding, class, OS ABI
+are derived from the kernel binary that was passed to
+.Xr kvm_open 3 .
+.Ss Kernel Displacement
+When the
+.Fa apply_kerndisp
+argument is true, the function queries the kernel displacement
+via
+.Xr kvm_kerndisp 3
+and subtracts it from every virtual address written into the ELF core.
+This maps runtime addresses back to the linked symbol addresses in the
+kernel binary, allowing debuggers to resolve symbols without manual
+adjustment.
+.Pp
+When
+.Fa apply_kerndisp
+is false, the raw runtime virtual addresses from the minidump are
+preserved in the output.
+This may be useful for tools that need the actual addresses as they
+were at the time of the crash.
+.Sh RETURN VALUES
+The
+.Fn kvm_minidump_to_elf
+function returns 0 on success.
+On failure it returns \-1 and sets an error string that can be
+retrieved with
+.Xr kvm_geterr 3 .
+.Sh EXAMPLES
+Convert a minidump to an ELF core file with kernel displacement adjustment:
+.Bd -literal -offset indent
+#include <fcntl.h>
+#include <kvm.h>
+#include <limits.h>
+#include <stdio.h>
+
+char errbuf[_POSIX2_LINE_MAX];
+kvm_t *kd;
+
+kd = kvm_open("/boot/kernel/kernel",
+ "/var/crash/vmcore.0", NULL, O_RDONLY, errbuf);
+if (kd == NULL) {
+ fprintf(stderr, "kvm_open: %s\en", errbuf);
+ return (1);
+}
+
+if (kvm_minidump_to_elf(kd, "vmcore.0.elf", true) != 0) {
+ fprintf(stderr, "kvm_minidump_to_elf: %s\en",
+ kvm_geterr(kd));
+ kvm_close(kd);
+ return (1);
+}
+
+kvm_close(kd);
+.Ed
+.Pp
+Preserve raw runtime addresses (no kernel displacement adjustment):
+.Bd -literal -offset indent
+kvm_minidump_to_elf(kd, "vmcore.0.raw.elf", false);
+.Ed
+.Sh ERRORS
+The
+.Fn kvm_minidump_to_elf
+function will fail if:
+.Bl -tag -width Er
+.It Bq Er ENOMEM
+Insufficient memory was available to allocate internal data structures
+or to buffer the minidump page ranges.
+.It Bq Er EIO
+A read from the minidump via
+.Xr kvm_read2 3
+returned fewer bytes than expected.
+.El
+.Pp
+Errors from
+.Xr open 2 ,
+.Xr elf_begin 3 ,
+and related ELF library functions are also reported through
+.Xr kvm_geterr 3 .
+.Sh SEE ALSO
+.Xr kvm 3 ,
+.Xr kvm_close 3 ,
+.Xr kvm_geterr 3 ,
+.Xr kvm_kerndisp 3 ,
+.Xr kvm_open 3 ,
+.Xr kvm_walk_pages 3 ,
+.Sh HISTORY
+The
+.Fn kvm_minidump_to_elf
+function first appeared in
+.Fx 15.1 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Fn kvm_minidump_to_elf
+function was developed by the FreeBSD Foundation.
+The minidump-to-elf code was originally written by
+.An Bora Ozarslan Aq Mt borako.ozarslan@gmail.com
+and later ported to libkvm with improvements by
+.An Minsoo Choo Aq Mt minsoochoo0122@proton.me .
+.Pp
+This manual page was written by
+.An Minsoo Choo Aq Mt minsoochoo0122@proton.me .
diff --git a/lib/libkvm/kvm_minidump_to_elf.c b/lib/libkvm/kvm_minidump_to_elf.c
new file mode 100644
--- /dev/null
+++ b/lib/libkvm/kvm_minidump_to_elf.c
@@ -0,0 +1,392 @@
+/*
+ * Copyright (c) 2019, 2026 The FreeBSD Foundation
+ *
+ * This software was developed by Bora Ozarslan and Minsoo Choo under
+ * sponsorship from the FreeBSD Foundation.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/types.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <kvm.h>
+#include <libelf.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <vm/vm.h>
+
+#include "kvm_private.h"
+
+#define PAGESIZE 4096
+
+/* A range is a contiguous set of pages in the minidump. */
+struct range_info {
+ u_long vaddr; /* Virtual address of the range. */
+ u_long off; /* Offset in the original core file. */
+ u_char prot; /* ELF protection flags for the range. */
+ void *buf; /* Buffer holding data to write out. */
+ size_t rangelen; /* Total length of the range (memsz). */
+ size_t filelen; /* Length of data present in file (filesz). */
+};
+
+struct walk_context {
+ struct range_info *ranges;
+ int len;
+ int cap;
+};
+
+static int
+range_cmp(const void *a, const void *b)
+{
+ const struct range_info *ra = a;
+ const struct range_info *rb = b;
+
+ if (ra->vaddr < rb->vaddr)
+ return (-1);
+ if (ra->vaddr > rb->vaddr)
+ return (1);
+ return (0);
+}
+
+static void
+merge_ranges(struct walk_context *ctx)
+{
+ struct range_info *r;
+ int i, j, in_memory, next_in_memory;
+
+ if (ctx->len < 2)
+ return;
+
+ r = ctx->ranges;
+ qsort(r, ctx->len, sizeof(*r), range_cmp);
+
+ j = 0;
+ for (i = 1; i < ctx->len; i++) {
+ in_memory = (r[j].off != (u_long)-1);
+ next_in_memory = (r[i].off != (u_long)-1);
+
+ if (r[j].vaddr + r[j].rangelen == r[i].vaddr &&
+ r[j].prot == r[i].prot &&
+ (in_memory || !next_in_memory)) {
+ r[j].rangelen += r[i].rangelen;
+ r[j].filelen += r[i].filelen;
+ if (!in_memory && next_in_memory)
+ r[j].off = (u_long)-1;
+ } else {
+ j++;
+ if (j != i)
+ r[j] = r[i];
+ }
+ }
+ ctx->len = j + 1;
+}
+
+static int
+walk_pages_cb(struct kvm_page *page, void *arg)
+{
+ struct walk_context *ctx = arg;
+ struct range_info *r, *newranges;
+ int not_in_memory;
+ u_char prot;
+
+ if (page->kp_kmap_vaddr == 0)
+ return (1);
+
+ prot = 0;
+ if (page->kp_prot & VM_PROT_READ)
+ prot |= PF_R;
+ if (page->kp_prot & VM_PROT_WRITE)
+ prot |= PF_W;
+ if (page->kp_prot & VM_PROT_EXECUTE)
+ prot |= PF_X;
+
+ not_in_memory = (page->kp_offset == -1);
+
+ /* Try to extend an existing range. */
+ for (int i = ctx->len - 1; i >= 0; --i) {
+ r = &ctx->ranges[i];
+
+ if (r->vaddr == page->kp_kmap_vaddr)
+ return (-1); /* duplicate vaddr */
+
+ if (r->vaddr + r->rangelen == page->kp_kmap_vaddr &&
+ prot == r->prot) {
+ if (not_in_memory && r->off != (u_long)-1)
+ continue;
+ if (not_in_memory)
+ r->off = page->kp_offset;
+ r->rangelen += page->kp_len;
+ if (r->off != (u_long)-1)
+ r->filelen += page->kp_len;
+ return (1);
+ }
+ }
+
+ /* Start a new range, growing the array if needed. */
+ if (ctx->len == ctx->cap) {
+ ctx->cap *= 2;
+ newranges = realloc(ctx->ranges,
+ ctx->cap * sizeof(struct range_info));
+ if (newranges == NULL)
+ return (-1);
+ ctx->ranges = newranges;
+ }
+
+ r = &ctx->ranges[ctx->len];
+ r->vaddr = page->kp_kmap_vaddr;
+ r->rangelen = page->kp_len;
+ r->filelen = not_in_memory ? 0 : page->kp_len;
+ r->off = page->kp_offset;
+ r->prot = prot;
+ r->buf = NULL;
+
+ ctx->len++;
+ return (1);
+}
+
+static void
+free_ranges(struct walk_context *ctx)
+{
+ if (ctx->ranges != NULL) {
+ for (int i = 0; i < ctx->len; i++)
+ free(ctx->ranges[i].buf);
+ free(ctx->ranges);
+ }
+ ctx->ranges = NULL;
+ ctx->len = 0;
+ ctx->cap = 0;
+}
+
+int
+kvm_minidump_to_elf(kvm_t *kd, const char *outfile, bool apply_kerndisp)
+{
+ struct walk_context ctx;
+ Elf *e = NULL;
+ GElf_Ehdr *ehdr;
+ GElf_Phdr phdr;
+ GElf_Shdr shdr;
+ Elf_Scn *section;
+ Elf_Data *data;
+ kssize_t kerndisp;
+ size_t cr;
+ int elf_class, fd = 1, ret = -1;
+
+ /*
+ * Verify that the descriptor refers to a minidump, not a
+ * live system or a full core dump.
+ */
+ if (!_kvm_is_minidump(kd)) {
+ _kvm_err(kd, kd->program,
+ "minidump_to_elf: not a minidump");
+ return -1;
+ }
+
+ memset(&ctx, 0, sizeof(ctx));
+
+ /* Collect memory ranges from the minidump. */
+ ctx.cap = 128;
+ ctx.ranges = calloc(ctx.cap, sizeof(struct range_info));
+ if (ctx.ranges == NULL) {
+ _kvm_syserr(kd, kd->program, "calloc");
+ goto out;
+ }
+
+ if (kvm_walk_pages(kd, walk_pages_cb, &ctx) == 0) {
+ _kvm_err(kd, kd->program, "kvm_walk_pages failed");
+ goto out;
+ }
+
+ merge_ranges(&ctx);
+
+ /*
+ * Get the kernel displacement if requested. When
+ * enabled, subtract it from every virtual address so the
+ * ELF core matches the linked symbols.
+ */
+ kerndisp = apply_kerndisp ? kvm_kerndisp(kd) : 0;
+
+ /*
+ * The kernel ELF header is already available in kd->nlehdr,
+ * populated by libkvm when the descriptor was opened.
+ */
+ elf_class = kd->nlehdr.e_ident[EI_CLASS];
+
+ /* Open the output file. */
+ if ((fd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) {
+ _kvm_syserr(kd, kd->program, "%s", outfile);
+ goto out;
+ }
+
+ if (elf_version(EV_CURRENT) == EV_NONE) {
+ _kvm_err(kd, kd->program, "ELF library init failed: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+
+ if ((e = elf_begin(fd, ELF_C_WRITE, NULL)) == NULL) {
+ _kvm_err(kd, kd->program, "elf_begin: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+
+ if ((ehdr = gelf_newehdr(e, elf_class)) == NULL) {
+ _kvm_err(kd, kd->program, "gelf_newehdr: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+
+ ehdr->e_ident[EI_DATA] = kd->nlehdr.e_ident[EI_DATA];
+ ehdr->e_ident[EI_CLASS] = elf_class;
+ ehdr->e_machine = kd->nlehdr.e_machine;
+ ehdr->e_type = ET_CORE;
+ ehdr->e_ident[EI_OSABI] = kd->nlehdr.e_ident[EI_OSABI];
+
+ if (gelf_newphdr(e, ctx.len + 1) == NULL) {
+ _kvm_err(kd, kd->program, "gelf_newphdr: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+
+ /* Create sections holding the data for each memory range. */
+ for (int i = 0; i < ctx.len; i++) {
+ ctx.ranges[i].buf = malloc(ctx.ranges[i].filelen);
+ if (ctx.ranges[i].buf == NULL) {
+ _kvm_syserr(kd, kd->program, "malloc");
+ goto out;
+ }
+
+ cr = kvm_read2(kd, ctx.ranges[i].vaddr,
+ ctx.ranges[i].buf, ctx.ranges[i].filelen);
+ if (cr != ctx.ranges[i].filelen) {
+ _kvm_err(kd, kd->program, "kvm_read2 failed");
+ goto out;
+ }
+
+ if ((section = elf_newscn(e)) == NULL) {
+ _kvm_err(kd, kd->program, "elf_newscn: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+
+ if ((data = elf_newdata(section)) == NULL) {
+ _kvm_err(kd, kd->program, "elf_newdata: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+
+ data->d_align = PAGESIZE;
+ data->d_off = 0LL;
+ data->d_buf = ctx.ranges[i].buf;
+ data->d_type = ELF_T_WORD;
+ data->d_size = ctx.ranges[i].filelen;
+ data->d_version = EV_CURRENT;
+
+ if (gelf_getshdr(section, &shdr) != &shdr) {
+ _kvm_err(kd, kd->program, "gelf_getshdr: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+
+ shdr.sh_name = 0;
+ shdr.sh_type = SHT_PROGBITS;
+ shdr.sh_flags = SHF_OS_NONCONFORMING;
+ shdr.sh_entsize = 0;
+ shdr.sh_addr = ctx.ranges[i].vaddr - kerndisp;
+ if (gelf_update_shdr(section, &shdr) == 0) {
+ _kvm_err(kd, kd->program, "gelf_update_shdr: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+ }
+
+ /* Compute layout so section offsets are available. */
+ if (elf_update(e, ELF_C_NULL) < 0) {
+ _kvm_err(kd, kd->program, "elf_update(NULL): %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+
+ /*
+ * Fill in PT_LOAD program headers. Virtual addresses are
+ * adjusted by the kernel displacement so they align with
+ * the linked symbol addresses in the kernel binary.
+ */
+ section = NULL;
+ for (int i = 0; i < ctx.len; i++) {
+ if ((section = elf_nextscn(e, section)) == NULL) {
+ _kvm_err(kd, kd->program,
+ "could not get section %d", i);
+ goto out;
+ }
+
+ if (gelf_getphdr(e, i, &phdr) != &phdr) {
+ _kvm_err(kd, kd->program, "gelf_getphdr: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+
+ if (gelf_getshdr(section, &shdr) != &shdr) {
+ _kvm_err(kd, kd->program, "gelf_getshdr: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+
+ data = elf_getdata(section, NULL);
+
+ phdr.p_type = PT_LOAD;
+ phdr.p_flags = ctx.ranges[i].prot;
+ phdr.p_offset = shdr.sh_offset + data->d_off;
+ phdr.p_vaddr = ctx.ranges[i].vaddr - kerndisp;
+ phdr.p_paddr = 0;
+ phdr.p_filesz = ctx.ranges[i].filelen;
+ phdr.p_memsz = ctx.ranges[i].rangelen;
+ phdr.p_align = PAGESIZE;
+ if (gelf_update_phdr(e, i, &phdr) == 0) {
+ _kvm_err(kd, kd->program, "gelf_update_phdr: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+ }
+
+ /* PT_PHDR entry describing the program header table itself. */
+ if (gelf_getphdr(e, ctx.len, &phdr) != &phdr) {
+ _kvm_err(kd, kd->program, "gelf_getphdr: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+ phdr.p_type = PT_PHDR;
+ phdr.p_flags = PF_R;
+ phdr.p_offset = ehdr->e_phoff;
+ phdr.p_vaddr = 0;
+ phdr.p_paddr = 0;
+ phdr.p_filesz = gelf_fsize(e, ELF_T_PHDR, ctx.len + 1,
+ EV_CURRENT);
+ phdr.p_memsz = phdr.p_filesz;
+ phdr.p_align = 8;
+ if (gelf_update_phdr(e, ctx.len, &phdr) == 0) {
+ _kvm_err(kd, kd->program, "gelf_update_phdr: %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+
+ elf_flagphdr(e, ELF_C_SET, ELF_F_DIRTY);
+
+ if (elf_update(e, ELF_C_WRITE) < 0) {
+ _kvm_err(kd, kd->program, "elf_update(WRITE): %s",
+ elf_errmsg(-1));
+ goto out;
+ }
+
+ ret = 0;
+
+out:
+ if (e != NULL)
+ elf_end(e);
+ if (fd >= 0)
+ close(fd);
+ free_ranges(&ctx);
+ return (ret);
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Apr 6, 7:07 PM (21 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
30994017
Default Alt Text
D56250.diff (14 KB)
Attached To
Mode
D56250: kvm: introduce kvm_minidump_to_elf()
Attached
Detach File
Event Timeline
Log In to Comment