exterror(9): dynamic kernel categories

Make it possible to define categories without compiling their
paths into libc (important for third-party modules).  The
EXTERR_CATEGORY_DYNAMIC macro can be defined to a string describing the
compilation unit (generally the path relative to src/sys) which takes
the place of EXTERR_CATEGORY.

These strings are assembled in linker sets with category numbers
assigned at system startup or module load time.  The strings can be
retrieved from the kern.exterr.categories.<category> sysctl.

Reviewed by:	kib
Sponsored by:	Innovate UK
Differential Revision:	https://reviews.freebsd.org/D58237
This commit is contained in:
Brooks Davis committed 2026-08-03 22:43:23 +01:00
1 parent bbf95a9b84
commit 295f102309
6 files changed
+264 -2

No files matched your search

+9 -2
View File
@@ -6,7 +6,7 @@
.\" Konstantin Belousov <[email protected]> under sponsorship
.\" from the FreeBSD Foundation.
.\"
.Dd July 21, 2026
.Dd August 3, 2026
.Dt EXTERROR 9
.Os
.Sh NAME
@@ -14,6 +14,7 @@
.Nd provide extended error information to userspace
.Sh SYNOPSIS
.Bd -literal -offset left -compact
#define EXTERR_CATEGORY_DYNAMIC "path/to/this/file.c"
#define EXTERR_CATEGORY EXTERR_CAT_MYCATEGORY
.Ed
.In sys/exterrvar.h
@@ -56,7 +57,13 @@ is to make it easier for a user to identify the cause of the error.
Before
.Nm
can be used in the given source .c file, the category of extended errors
should be allocated in the
must be defined.
This can be done by setting the
.Va EXTERR_CATEGORY_DYNAMIC
macro to a string containing the name of the file relative to
.Pa src/sys
directory.
Alternatively, an entry may be allocated in the
.In sys/exterr_cat.h
file.
The category is the unique integer, that, together with the source
+1
View File
@@ -3988,6 +3988,7 @@ kern/subr_disk.c standard
kern/subr_early.c standard
kern/subr_epoch.c standard
kern/subr_eventhandler.c standard
kern/subr_exterr.c standard
kern/subr_fattime.c standard
kern/subr_firmware.c optional firmware
kern/subr_filter.c standard
+36
View File
@@ -331,6 +331,40 @@ linker_file_register_sysctls(linker_file_t lf, bool enable)
sx_xlock(&kld_sx);
}
static void
linker_file_register_exterr(linker_file_t lf)
{
struct exterr_cat **start, **stop;
KLD_DPF(FILE,
(__func__ ": registering exterror categories for %s\n",
lf->filename));
sx_assert(&kld_sx, SA_XLOCKED);
if (linker_file_lookup_set(lf, "exterr_cats", &start, &stop, NULL) != 0)
return;
exterr_cat_register_module(start, stop);
}
static void
linker_file_unregister_exterr(linker_file_t lf)
{
struct exterr_cat **start, **stop;
KLD_DPF(FILE,
(__func__ ": unregistering exterror categories for %s\n",
lf->filename));
sx_assert(&kld_sx, SA_XLOCKED);
if (linker_file_lookup_set(lf, "exterr_cats", &start, &stop, NULL) != 0)
return;
exterr_cat_unregister_module(start, stop);
}
/*
* Invoke the LINKER_CTF_GET implementation for this file. Existing
* implementations will load CTF info from the filesystem upon the first call
@@ -500,6 +534,7 @@ linker_load_file(const char *filename, linker_file_t *result)
#ifdef VIMAGE
LINKER_PROPAGATE_VNETS(lf);
#endif
linker_file_register_exterr(lf);
linker_file_sysinit(lf);
lf->flags |= LINKER_FILE_LINKED;
@@ -775,6 +810,7 @@ linker_file_unload(linker_file_t file, int flags)
if ((file->flags & LINKER_FILE_LINKED) != 0) {
file->flags &= ~LINKER_FILE_LINKED;
linker_file_unregister_sysctls(file);
linker_file_unregister_exterr(file);
linker_file_sysuninit(file);
EVENTHANDLER_INVOKE(kld_unload, file->filename, file->address,
file->size);
+169
View File
@@ -0,0 +1,169 @@
/*-
* Copyright (c) 2026 Capabilities Limited
*
* SPDX-License-Identifier: BSD-2-Clause
*
* This software was developed by Capabilities Limited with funding from
* Innovate UK and the Department for Science, Innovation and Technology
* for the adoption and diffusion of CHERI technology under project
* 10168042 (“CheriBSD feature extraction, maturity, and testing”).
*
*/
#define EXTERR_CATEGORY_DYNAMIC "kern/subr_exterr.c"
#include <sys/param.h>
#include <sys/exterrvar.h>
#include <sys/exterr_cat.h>
#include <sys/kernel.h>
#include <sys/libkern.h>
#include <sys/linker_set.h>
#include <sys/malloc.h>
#include <sys/linker.h> /* Need MALLOC_DECLARE */
#include <sys/rwlock.h>
#include <sys/stddef.h>
#include <sys/sysctl.h>
struct exterr_cat_span {
unsigned int first;
unsigned int count;
struct exterr_cat **cat_sets;
TAILQ_ENTRY(exterr_cat_span) entries;
};
TAILQ_HEAD(exterr_cat_span_head, exterr_cat_span) cat_span_head;
SET_DECLARE(exterr_cats, struct exterr_cat);
static struct exterr_cat_span kern_cats;
unsigned int ncats;
struct rwlock cat_lock;
static bool
exterr_cat_register_set(struct exterr_cat_span *span, struct exterr_cat **start,
struct exterr_cat **stop)
{
struct exterr_cat **catp;
ptrdiff_t count;
count = stop - start;
if (count < 1)
return (true);
rw_wlock(&cat_lock);
if (ncats + count < ncats) {
printf("too many exterror categories\n");
rw_wunlock(&cat_lock);
return (false);
}
span->first = ncats + 1;
for (catp = start; catp < stop; catp++)
(*catp)->cat = ++ncats;
span->count = count;
span->cat_sets = start;
TAILQ_INSERT_TAIL(&cat_span_head, span, entries);
rw_wunlock(&cat_lock);
return (true);
}
void
exterr_cat_register_module(struct exterr_cat **start, struct exterr_cat **stop)
{
struct exterr_cat_span *span;
span = malloc(sizeof(*span), M_LINKER, M_WAITOK | M_ZERO);
if (!exterr_cat_register_set(span, start, stop))
free(span, M_LINKER);
}
void
exterr_cat_unregister_module(struct exterr_cat **start,
struct exterr_cat **stop)
{
struct exterr_cat_span *span;
if (stop - start < 1)
return;
rw_wlock(&cat_lock);
TAILQ_FOREACH(span, &cat_span_head, entries) {
if (span->cat_sets == start) {
MPASS(span->first > 1);
MPASS(span->count == stop - start);
TAILQ_REMOVE(&cat_span_head, span, entries);
break;
}
}
KASSERT(span != NULL, ("start not found in spans"));
/*
* NB: we leak category numbers on module unload because we can't
* reasonably know which ones are in use in running software.
*/
rw_wunlock(&cat_lock);
free(span, M_LINKER);
}
static void
exterr_cat_register_kern(void *arg)
{
rw_init(&cat_lock, "exterr dynamic categories");
TAILQ_INIT(&cat_span_head);
if (SET_COUNT(exterr_cats) == 0)
return;
exterr_cat_register_set(&kern_cats, SET_BEGIN(exterr_cats),
SET_LIMIT(exterr_cats));
}
SYSINIT(exterr, SI_SUB_KMEM, SI_ORDER_FIRST, exterr_cat_register_kern, NULL);
static int
sysctl_exterr_categories(SYSCTL_HANDLER_ARGS)
{
struct exterr_cat_span *span;
const struct exterr_cat *cat = NULL;
int idx;
if (arg2 != 1)
return (EXTERROR(EINVAL,
"too many args to kern.exterr.categories %d", arg2));
idx = *(int *)arg1;
rw_rlock(&cat_lock);
if (idx < 1 || idx > ncats) {
rw_runlock(&cat_lock);
return (EXTERROR(EINVAL, "category %d out of range (1...%d)",
idx, ncats));
}
TAILQ_FOREACH(span, &cat_span_head, entries) {
if (idx < span->first)
break; /* Not here any more */
if (idx < span->first + span->count)
cat = span->cat_sets[idx - span->first];
}
rw_runlock(&cat_lock);
if (cat == NULL)
return (EXTERROR(ENOENT, "category not found %d", idx));
MPASS(cat->cat == idx);
return (SYSCTL_OUT(req, cat->file, strlen(cat->file) + 1));
}
SYSCTL_NODE(_kern, OID_AUTO, exterr, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
"Extended error information");
SYSCTL_UINT(_kern_exterr, OID_AUTO, ncategories, CTLFLAG_RD | CTLFLAG_MPSAFE,
&ncats, 0, "Number of dynamic categories");
SYSCTL_NODE(_kern_exterr, OID_AUTO, categories, CTLFLAG_RD | CTLFLAG_MPSAFE,
sysctl_exterr_categories, "Extended error categories");
+22
View File
@@ -14,6 +14,9 @@
#include <sys/_exterr.h>
#include <sys/_uexterror.h>
#include <sys/exterr_cat.h>
#ifdef EXTERR_CATEGORY_DYNAMIC
#include <sys/linker_set.h>
#endif
#define UEXTERROR_MAXLEN 256
@@ -27,8 +30,23 @@
#ifdef _KERNEL
struct exterr_cat {
unsigned int cat;
const char *file;
};
struct thread;
#ifdef EXTERR_CATEGORY_DYNAMIC
#ifdef EXTERR_STRINGS
static struct exterr_cat __dynamic_cat = { .file = EXTERR_CATEGORY_DYNAMIC };
DATA_WSET(exterr_cats, __dynamic_cat);
#define EXTERR_CATEGORY (__dynamic_cat.cat | EXTERR_CAT_SRC_KERN_DYNAMIC)
#else
#define EXTERR_CATEGORY EXTERR_CAT_NONE
#endif
#endif
#ifndef EXTERR_CATEGORY
#error "Specify error category before including sys/exterrvar.h"
#endif
@@ -78,6 +96,10 @@ int exterr_set(int eerror, int category, const char *mmsg, uint64ptr_t pp1,
uint64ptr_t pp2, int line);
int exterr_to_ue(struct thread *td, struct uexterror *ue);
void ktrexterr(struct thread *td);
void exterr_cat_register_module(struct exterr_cat **start,
struct exterr_cat **stop);
void exterr_cat_unregister_module(struct exterr_cat **start,
struct exterr_cat **stop);
#else /* !_KERNEL */
+27
View File
@@ -23,8 +23,10 @@
* SUCH DAMAGE.
*/
#include <sys/param.h>
#include <sys/exterrvar.h>
#include <sys/mman.h>
#include <sys/sysctl.h>
#include <atf-c.h>
#include <errno.h>
@@ -121,11 +123,36 @@ ATF_TC_BODY(gettext_noextended_after_extended, tc)
ATF_CHECK_STREQ(exterr, "");
}
ATF_TC(exterr_dynamic_categories);
ATF_TC_HEAD(exterr_dynamic_categories, tc)
{
atf_tc_set_md_var(tc, "descr",
"directly check there is at least one registered category");
}
ATF_TC_BODY(exterr_dynamic_categories, tc)
{
int mib[4];
size_t len;
char filename_buf[128];
len = nitems(mib);
ATF_REQUIRE_EQ(sysctlnametomib("kern.exterr.categories", mib, &len),
0);
mib[3] = 1;
len = sizeof(filename_buf);
ATF_REQUIRE_EQ(sysctl(mib, nitems(mib), filename_buf, &len, NULL, 0),
0);
printf("%s\n", filename_buf);
/* We can't know what it is, but make sure it's non-empty */
ATF_REQUIRE(strlen(filename_buf) > 1);
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, gettext_extended);
ATF_TP_ADD_TC(tp, gettext_noextended);
ATF_TP_ADD_TC(tp, gettext_noextended_after_extended);
ATF_TP_ADD_TC(tp, exterr_dynamic_categories);
return (atf_no_error());
}