git: 295f10230903 - main - exterror(9): dynamic kernel categories
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 03 Aug 2026 21:44:06 UTC
The branch main has been updated by brooks:
URL: https://cgit.FreeBSD.org/src/commit/?id=295f10230903d54c700518467c4ea4492f5c4faa
commit 295f10230903d54c700518467c4ea4492f5c4faa
Author: Brooks Davis <brooks@FreeBSD.org>
AuthorDate: 2026-08-03 16:50:01 +0000
Commit: Brooks Davis <brooks@FreeBSD.org>
CommitDate: 2026-08-03 21:43:23 +0000
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
---
share/man/man9/exterror.9 | 11 ++-
sys/conf/files | 1 +
sys/kern/kern_linker.c | 36 +++++++++
sys/kern/subr_exterr.c | 169 +++++++++++++++++++++++++++++++++++++++++++
sys/sys/exterrvar.h | 22 ++++++
tests/sys/kern/exterr_test.c | 27 +++++++
6 files changed, 264 insertions(+), 2 deletions(-)
diff --git a/share/man/man9/exterror.9 b/share/man/man9/exterror.9
index 2c9dc73f7616..a7e63abab577 100644
--- a/share/man/man9/exterror.9
+++ b/share/man/man9/exterror.9
@@ -6,7 +6,7 @@
.\" Konstantin Belousov <kib@FreeBSD.org> 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
diff --git a/sys/conf/files b/sys/conf/files
index 1c52b432442f..165bcba4356b 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -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
diff --git a/sys/kern/kern_linker.c b/sys/kern/kern_linker.c
index b183d18eaea6..69b47fa6dd17 100644
--- a/sys/kern/kern_linker.c
+++ b/sys/kern/kern_linker.c
@@ -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);
diff --git a/sys/kern/subr_exterr.c b/sys/kern/subr_exterr.c
new file mode 100644
index 000000000000..b704d3ba1dae
--- /dev/null
+++ b/sys/kern/subr_exterr.c
@@ -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");
diff --git a/sys/sys/exterrvar.h b/sys/sys/exterrvar.h
index a4f6d5313efc..e0822288d746 100644
--- a/sys/sys/exterrvar.h
+++ b/sys/sys/exterrvar.h
@@ -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 */
diff --git a/tests/sys/kern/exterr_test.c b/tests/sys/kern/exterr_test.c
index bde7654fecfc..fad002767163 100644
--- a/tests/sys/kern/exterr_test.c
+++ b/tests/sys/kern/exterr_test.c
@@ -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());
}