git: 295f10230903 - main - exterror(9): dynamic kernel categories
Brooks Davis <[email protected]> Mon, 03 Aug 2026 21:44:06 +0000
| Newsgroups | gmane.os.freebsd.devel.cvs.src |
|---|---|
| Message-ID | <6a710ba6.3bc5b.16ea80c3__23676.0827641532$1785793518$gmane$org@gitrepo.freebsd.org> |
The branch main has been updated by brooks: URL: https://cgit.FreeBSD.org/src/commit/?id=295f10230903d54c700518467c4ea4492f5c4faa commit 295f10230903d54c700518467c4ea4492f5c4faa Author: Brooks Davis <[email protected]> AuthorDate: 2026-08-03 16:50:01 +0000 Commit: Brooks Davis <[email protected]> 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 <[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 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()); }