Add a routine to dump boot metadata

The boot metadata (also referred to as modinfo, or preload metadata)
provides information about the size and location of the kernel,
pre-loaded modules, and other metadata (e.g. the EFI framebuffer) to be
consumed during by the kernel during early boot. It is encoded as a
series of type-length-value entries and is usually constructed by
loader(8) and passed to the kernel. It is also faked on some
architectures when booted by other means.

Although much of the module information is available via kldstat(8),
there is no easy way to debug the metadata in its entirety. Add some
routines to parse this data and allow it to be printed to the console
during early boot or output via a sysctl.

Since the output can be lengthly, printing to the console is gated
behind the debug.dump_modinfo_at_boot kenv variable as well as the
BOOTVERBOSE flag. The sysctl to print the metadata is named
debug.dump_modinfo.

Reviewed by:	tsoome
Sponsored by:	NetApp, Inc.
Sponsored by:	Klara, Inc.
Differential Revision:	https://reviews.freebsd.org/D26687
This commit is contained in:
Mitchell Horne 2020-10-08 18:02:05 +00:00
parent 7eb9f7558c
commit 22e6a67086
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=366542
6 changed files with 289 additions and 0 deletions

View File

@ -1853,6 +1853,15 @@ hammer_time(u_int64_t modulep, u_int64_t physfree)
if (late_console)
cninit();
/*
* Dump the boot metadata. We have to wait for cninit() since console
* output is required. If it's grossly incorrect the kernel will never
* make it this far.
*/
if ((boothowto & RB_VERBOSE) &&
getenv_is_true("debug.dump_modinfo_at_boot"))
preload_dump();
#ifdef DEV_ISA
#ifdef DEV_ATPIC
elcr_probe();

View File

@ -1027,6 +1027,15 @@ initarm(struct arm_boot_params *abp)
debugf(" dtbp = 0x%08x\n", (uint32_t)dtbp);
arm_print_kenv();
/*
* Dump the boot metadata. We have to wait for cninit() since console
* output is required. If it's grossly incorrect the kernel will never
* make it this far.
*/
if ((boothowto & RB_VERBOSE) &&
getenv_is_true("debug.dump_modinfo_at_boot"))
preload_dump();
env = kern_getenv("kernelname");
if (env != NULL) {
strlcpy(kernelname, env, sizeof(kernelname));

View File

@ -1242,6 +1242,15 @@ initarm(struct arm64_bootparams *abp)
panic("Invalid bus configuration: %s",
kern_getenv("kern.cfg.order"));
/*
* Dump the boot metadata. We have to wait for cninit() since console
* output is required. If it's grossly incorrect the kernel will never
* make it this far.
*/
if ((boothowto & RB_VERBOSE) &&
getenv_is_true("debug.dump_modinfo_at_boot"))
preload_dump();
init_proc0(abp->kern_stack);
msgbufinit(msgbufp, msgbufsize);
mutex_init();

View File

@ -3,6 +3,8 @@
*
* Copyright (c) 1998 Michael Smith
* All rights reserved.
* Copyright (c) 2020 NetApp Inc.
* Copyright (c) 2020 Klara Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@ -32,6 +34,10 @@ __FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/linker.h>
#include <sys/sbuf.h>
#include <sys/sysctl.h>
#include <machine/metadata.h>
#include <vm/vm.h>
#include <vm/vm_extern.h>
@ -304,3 +310,249 @@ preload_bootstrap_relocate(vm_offset_t offset)
}
}
}
/*
* Parse the modinfo type and append to the provided sbuf.
*/
static void
preload_modinfo_type(struct sbuf *sbp, int type)
{
if ((type & MODINFO_METADATA) == 0) {
switch (type) {
case MODINFO_END:
sbuf_cat(sbp, "MODINFO_END");
break;
case MODINFO_NAME:
sbuf_cat(sbp, "MODINFO_NAME");
break;
case MODINFO_TYPE:
sbuf_cat(sbp, "MODINFO_TYPE");
break;
case MODINFO_ADDR:
sbuf_cat(sbp, "MODINFO_ADDR");
break;
case MODINFO_SIZE:
sbuf_cat(sbp, "MODINFO_SIZE");
break;
case MODINFO_EMPTY:
sbuf_cat(sbp, "MODINFO_EMPTY");
break;
case MODINFO_ARGS:
sbuf_cat(sbp, "MODINFO_ARGS");
break;
default:
sbuf_cat(sbp, "unrecognized modinfo attribute");
}
return;
}
sbuf_cat(sbp, "MODINFO_METADATA | ");
switch (type & ~MODINFO_METADATA) {
case MODINFOMD_ELFHDR:
sbuf_cat(sbp, "MODINFOMD_ELFHDR");
break;
case MODINFOMD_SSYM:
sbuf_cat(sbp, "MODINFOMD_SSYM");
break;
case MODINFOMD_ESYM:
sbuf_cat(sbp, "MODINFOMD_ESYM");
break;
case MODINFOMD_DYNAMIC:
sbuf_cat(sbp, "MODINFOMD_DYNAMIC");
break;
case MODINFOMD_ENVP:
sbuf_cat(sbp, "MODINFOMD_ENVP");
break;
case MODINFOMD_HOWTO:
sbuf_cat(sbp, "MODINFOMD_HOWTO");
break;
case MODINFOMD_KERNEND:
sbuf_cat(sbp, "MODINFOMD_KERNEND");
break;
case MODINFOMD_SHDR:
sbuf_cat(sbp, "MODINFOMD_SHDR");
break;
case MODINFOMD_CTORS_ADDR:
sbuf_cat(sbp, "MODINFOMD_CTORS_ADDR");
break;
case MODINFOMD_CTORS_SIZE:
sbuf_cat(sbp, "MODINFOMD_CTORS_SIZE");
break;
case MODINFOMD_FW_HANDLE:
sbuf_cat(sbp, "MODINFOMD_FW_HANDLE");
break;
case MODINFOMD_KEYBUF:
sbuf_cat(sbp, "MODINFOMD_KEYBUF");
break;
#ifdef MODINFOMD_SMAP
case MODINFOMD_SMAP:
sbuf_cat(sbp, "MODINFOMD_SMAP");
break;
#endif
#ifdef MODINFOMD_SMAP_XATTR
case MODINFOMD_SMAP_XATTR:
sbuf_cat(sbp, "MODINFOMD_SMAP_XATTR");
break;
#endif
#ifdef MODINFOMD_DTBP
case MODINFOMD_DTBP:
sbuf_cat(sbp, "MODINFOMD_DTBP");
break;
#endif
#ifdef MODINFOMD_EFI_MAP
case MODINFOMD_EFI_MAP:
sbuf_cat(sbp, "MODINFOMD_EFI_MAP");
break;
#endif
#ifdef MODINFOMD_EFI_FB
case MODINFOMD_EFI_FB:
sbuf_cat(sbp, "MODINFOMD_EFI_FB");
break;
#endif
#ifdef MODINFOMD_MODULEP
case MODINFOMD_MODULEP:
sbuf_cat(sbp, "MODINFOMD_MODULEP");
break;
#endif
default:
sbuf_cat(sbp, "unrecognized metadata type");
}
}
/*
* Print the modinfo value, depending on type.
*/
static void
preload_modinfo_value(struct sbuf *sbp, uint32_t *bptr, int type, int len)
{
#ifdef __LP64__
#define sbuf_print_vmoffset(sb, o) sbuf_printf(sb, "0x%016lx", o);
#else
#define sbuf_print_vmoffset(sb, o) sbuf_printf(sb, "0x%08x", o);
#endif
switch (type) {
case MODINFO_NAME:
case MODINFO_TYPE:
case MODINFO_ARGS:
sbuf_printf(sbp, "%s", (char *)bptr);
break;
case MODINFO_SIZE:
case MODINFO_METADATA | MODINFOMD_CTORS_SIZE:
sbuf_printf(sbp, "%lu", *(u_long *)bptr);
break;
case MODINFO_ADDR:
case MODINFO_METADATA | MODINFOMD_SSYM:
case MODINFO_METADATA | MODINFOMD_ESYM:
case MODINFO_METADATA | MODINFOMD_DYNAMIC:
case MODINFO_METADATA | MODINFOMD_KERNEND:
case MODINFO_METADATA | MODINFOMD_ENVP:
case MODINFO_METADATA | MODINFOMD_CTORS_ADDR:
#ifdef MODINFOMD_SMAP
case MODINFO_METADATA | MODINFOMD_SMAP:
#endif
#ifdef MODINFOMD_SMAP_XATTR
case MODINFO_METADATA | MODINFOMD_SMAP_XATTR:
#endif
#ifdef MODINFOMD_DTBP
case MODINFO_METADATA | MODINFOMD_DTBP:
#endif
#ifdef MODINFOMD_EFI_FB
case MODINFO_METADATA | MODINFOMD_EFI_FB:
#endif
sbuf_print_vmoffset(sbp, *(vm_offset_t *)bptr);
break;
case MODINFO_METADATA | MODINFOMD_HOWTO:
sbuf_printf(sbp, "0x%08x", *bptr);
break;
case MODINFO_METADATA | MODINFOMD_SHDR:
case MODINFO_METADATA | MODINFOMD_ELFHDR:
case MODINFO_METADATA | MODINFOMD_FW_HANDLE:
case MODINFO_METADATA | MODINFOMD_KEYBUF:
#ifdef MODINFOMD_EFI_MAP
case MODINFO_METADATA | MODINFOMD_EFI_MAP:
#endif
/* Don't print data buffers. */
sbuf_cat(sbp, "buffer contents omitted");
break;
default:
break;
}
#undef sbuf_print_vmoffset
}
static void
preload_dump_internal(struct sbuf *sbp)
{
uint32_t *bptr, type, len;
KASSERT(preload_metadata != NULL,
("%s called without setting up preload_metadata", __func__));
/*
* Iterate through the TLV-encoded sections.
*/
bptr = (uint32_t *)preload_metadata;
sbuf_putc(sbp, '\n');
while (bptr[0] != MODINFO_END || bptr[0] != MODINFO_END) {
sbuf_printf(sbp, " %p:\n", bptr);
type = *bptr++;
len = *bptr++;
sbuf_printf(sbp, "\ttype:\t(%#04x) ", type);
preload_modinfo_type(sbp, type);
sbuf_putc(sbp, '\n');
sbuf_printf(sbp, "\tlen:\t%u\n", len);
sbuf_cat(sbp, "\tvalue:\t");
preload_modinfo_value(sbp, bptr, type, len);
sbuf_putc(sbp, '\n');
bptr += roundup(len, sizeof(u_long)) / sizeof(uint32_t);
}
}
/*
* Print the preloaded data to the console. Called from the machine-dependent
* initialization routines, e.g. hammer_time().
*/
void
preload_dump(void)
{
char buf[512];
struct sbuf sb;
/*
* This function is expected to be called before malloc is available,
* so use a static buffer and struct sbuf.
*/
sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN);
sbuf_set_drain(&sb, sbuf_printf_drain, NULL);
preload_dump_internal(&sb);
sbuf_finish(&sb);
sbuf_delete(&sb);
}
static int
sysctl_preload_dump(SYSCTL_HANDLER_ARGS)
{
struct sbuf sb;
int error;
if (preload_metadata == NULL)
return (EINVAL);
sbuf_new_for_sysctl(&sb, NULL, 512, req);
preload_dump_internal(&sb);
error = sbuf_finish(&sb);
sbuf_delete(&sb);
return (error);
}
SYSCTL_PROC(_debug, OID_AUTO, dump_modinfo,
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
NULL, 0, sysctl_preload_dump, "A",
"pretty-print the bootloader metadata");

View File

@ -949,6 +949,15 @@ initriscv(struct riscv_bootparams *rvbp)
cninit();
/*
* Dump the boot metadata. We have to wait for cninit() since console
* output is required. If it's grossly incorrect the kernel will never
* make it this far.
*/
if ((boothowto & RB_VERBOSE) &&
getenv_is_true("debug.dump_modinfo_at_boot"))
preload_dump();
init_proc0(rvbp->kern_stack);
msgbufinit(msgbufp, msgbufsize);

View File

@ -257,6 +257,7 @@ extern caddr_t preload_search_next_name(caddr_t _base);
extern caddr_t preload_search_info(caddr_t _mod, int _inf);
extern void preload_delete_name(const char *_name);
extern void preload_bootstrap_relocate(vm_offset_t _offset);
extern void preload_dump(void);
#ifdef KLD_DEBUG