Page MenuHomeFreeBSD

D56250.diff
No OneTemporary

D56250.diff

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

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)

Event Timeline