git: 1c5fea9e8b56 - main - queue(3): Debug macros: Finer control knobs, userland support

From: Olivier Certner <olce_at_FreeBSD.org>
Date: Mon, 28 Apr 2025 12:23:43 UTC
The branch main has been updated by olce:

URL: https://cgit.FreeBSD.org/src/commit/?id=1c5fea9e8b563186b8f5773064458c4ecf2d7004

commit 1c5fea9e8b563186b8f5773064458c4ecf2d7004
Author:     Olivier Certner <olce@FreeBSD.org>
AuthorDate: 2025-04-04 19:51:43 +0000
Commit:     Olivier Certner <olce@FreeBSD.org>
CommitDate: 2025-04-28 12:19:42 +0000

    queue(3): Debug macros: Finer control knobs, userland support
    
    Support enabling debugging macros for userland and _STANDALONE builds,
    in addition to the already existing kernel support.  On runtime error,
    panic() is used for kernel and _STANDALONE builds, and a simple
    fprintf() + abort() combination for userland ones.  These can be
    overriden if needed by defining the QMD_PANIC() and/or QMD_ASSERT()
    macros.
    
    The expansion of queue debug macros can now be controlled more finely
    thanks to the QUEUE_MACRO_DEBUG_ASSERTIONS and
    QUEUE_MACRO_NO_DEBUG_ASSERTIONS macros.  The first one serves to
    forcibly enable debug code and the second to forcibly disable it.  These
    are meant to be used as compile options only, and should normally not be
    defined in a source file.  It is an error to have both of them defined.
    
    If none of the two above-mentioned macros are defined, an automatic
    determination is performed.  When compiling kernel code,
    QUEUE_MACRO_DEBUG_ASSERTIONS is defined if INVARIANTS has been defined
    (as before).  For userland and _STANDALONE builds, no debug code is ever
    automatically inserted even if NDEBUG is not defined, as doing so would
    inflate code size and users may want to have working assert() calls
    without this overhead by default.
    
    In the manual page, document check code control under DIAGNOSTICS.
    While here, rework a bit the rest of the DIAGNOSTICS section.
    
    Reviewed by:    markj (older version)
    MFC after:      3 days
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D49973
---
 share/man/man3/queue.3 |  94 +++++++++++++------
 sys/sys/queue.h        | 249 ++++++++++++++++++++++++++-----------------------
 2 files changed, 199 insertions(+), 144 deletions(-)

diff --git a/share/man/man3/queue.3 b/share/man/man3/queue.3
index e0684fa7c70f..53b4a3eab120 100644
--- a/share/man/man3/queue.3
+++ b/share/man/man3/queue.3
@@ -1390,49 +1390,87 @@ while (n1 != NULL) {
 TAILQ_INIT(&head);
 .Ed
 .Sh DIAGNOSTICS
-When debugging
-.Nm queue(3) ,
-it can be useful to trace queue changes.
-To enable tracing, define the macro
-.Va QUEUE_MACRO_DEBUG_TRACE
-at compile time.
+.Nm queue(3)
+provides several diagnostic and debugging facilities.
 .Pp
-It can also be useful to trash pointers that have been unlinked from a queue,
-to detect use after removal.
-To enable pointer trashing, define the macro
-.Va QUEUE_MACRO_DEBUG_TRASH
-at compile time.
-The macro
-.Fn QMD_IS_TRASHED "void *ptr"
-returns true if
-.Fa ptr
-has been trashed by the
-.Va QUEUE_MACRO_DEBUG_TRASH
-option.
+Check code that performs basic integrity and API conformance checks is
+automatically inserted when using queue macros in the kernel if compiling it
+with
+.Va INVARIANTS .
+One can request insertion or elision of check code by respectively defining one
+of the macros
+.Va QUEUE_MACRO_DEBUG_ASSERTIONS
+or
+.Va QUEUE_MACRO_NO_DEBUG_ASSERTIONS
+before first inclusion of
+.In sys/queue.h .
+When check code encounters an anomaly, it panics the kernel or aborts the
+program.
+To this end, in the kernel or in
+.Va _STANDALONE
+builds, it by default calls
+.Fn panic ,
+while in userland builds it prints the diagnostic message on
+.Dv stderr
+and then calls
+.Fn abort .
+These behaviors can be overriden by defining a custom
+.Fn QMD_PANIC
+macro before first inclusion of
+.In sys/queue.h .
+The diagnostic messages automatically include the source file, line and function
+where the failing check occured.
+This behavior can be overriden by defining a custom
+.Fn QMD_ASSERT
+macro before first inclusion of
+.In sys/queue.h .
 .Pp
-In the kernel (with
-.Va INVARIANTS
-enabled), the
+The
 .Fn SLIST_REMOVE_PREVPTR
 macro is available to aid debugging:
 .Bl -hang -offset indent
 .It Fn SLIST_REMOVE_PREVPTR "TYPE **prev" "TYPE *elm" "SLIST_ENTRY NAME"
 .Pp
-Removes
+Removes element
 .Fa elm ,
 which must directly follow the element whose
 .Va &SLIST_NEXT()
 is
 .Fa prev ,
-from the SLIST.
-This macro validates that
+from the list.
+This macro may insert, under conditions detailed above, check code that
+validates that
 .Fa elm
-follows
+indeed follows
 .Fa prev
-in
-.Va INVARIANTS
-mode.
+in the list
+.Po
+through the
+.Fn QMD_SLIST_CHECK_PREVPTR
+macro
+.Pc .
 .El
+.Pp
+When debugging, it can be useful to trace queue changes.
+To enable tracing, define the macro
+.Va QUEUE_MACRO_DEBUG_TRACE .
+Note that, at the moment, only macros for regular tail queues have been
+instrumented.
+.Pp
+It can also be useful to trash pointers that have been unlinked from a queue,
+to detect use after removal.
+To enable pointer trashing, define the macro
+.Va QUEUE_MACRO_DEBUG_TRASH
+at compile time.
+Note that, at the moment, only a limited number of macros have been
+instrumented.
+The macro
+.Fn QMD_IS_TRASHED "void *ptr"
+returns true if
+.Fa ptr
+has been trashed by the
+.Va QUEUE_MACRO_DEBUG_TRASH
+option.
 .Sh SEE ALSO
 .Xr arb 3 ,
 .Xr tree 3
diff --git a/sys/sys/queue.h b/sys/sys/queue.h
index 307ac9f0ba44..b4814706cb76 100644
--- a/sys/sys/queue.h
+++ b/sys/sys/queue.h
@@ -116,11 +116,10 @@
  *
  */
 #ifdef QUEUE_MACRO_DEBUG
-#warn Use QUEUE_MACRO_DEBUG_TRACE and/or QUEUE_MACRO_DEBUG_TRASH
+#warn Use QUEUE_MACRO_DEBUG_xxx instead (TRACE, TRASH and/or ASSERTIONS)
 #define QUEUE_MACRO_DEBUG_TRACE
 #define QUEUE_MACRO_DEBUG_TRASH
 #endif
-
 #ifdef QUEUE_MACRO_DEBUG_TRACE
 /* Store the last 2 places the queue element or head was altered */
 struct qm_trace {
@@ -164,6 +163,62 @@ struct qm_trace {
 #define QMD_IS_TRASHED(x)	0
 #endif	/* QUEUE_MACRO_DEBUG_TRASH */
 
+#if defined(QUEUE_MACRO_DEBUG_ASSERTIONS) &&				\
+    defined(QUEUE_MACRO_NO_DEBUG_ASSERTIONS)
+#error Both QUEUE_MACRO_DEBUG_ASSERTIONS and QUEUE_MACRO_NO_DEBUG_ASSERTIONS defined
+#endif
+
+/*
+ * Automatically define QUEUE_MACRO_DEBUG_ASSERTIONS when compiling the kernel
+ * with INVARIANTS, if not already defined and not prevented by presence of
+ * QUEUE_MACRO_NO_DEBUG_ASSERTIONS.
+ */
+#if !defined(QUEUE_MACRO_DEBUG_ASSERTIONS) &&				\
+    !defined(QUEUE_MACRO_NO_DEBUG_ASSERTIONS) &&			\
+    (defined(_KERNEL) && defined(INVARIANTS))
+#define QUEUE_MACRO_DEBUG_ASSERTIONS
+#endif
+
+/*
+ * If queue assertions are enabled, provide default definitions for QMD_PANIC()
+ * and QMD_ASSERT() if undefined.
+ */
+#ifdef QUEUE_MACRO_DEBUG_ASSERTIONS
+#ifndef QMD_PANIC
+#if defined(_KERNEL) || defined(_STANDALONE)
+/*
+ * On _STANDALONE, either <stand.h> or the headers using <sys/queue.h> provide
+ * a declaration or macro for panic().
+ */
+#ifdef _KERNEL
+#include <sys/kassert.h>
+#endif
+#define QMD_PANIC(fmt, ...) do {					\
+	panic(fmt, ##__VA_ARGS__);					\
+} while (0)
+#else /* !(_KERNEL || _STANDALONE) */
+#include <stdio.h>
+#include <stdlib.h>
+#define QMD_PANIC(fmt, ...) do {					\
+	fprintf(stderr, fmt "\n", ##__VA_ARGS__);			\
+	abort();							\
+} while (0)
+#endif /* _KERNEL || _STANDALONE */
+#endif /* !QMD_PANIC */
+
+#ifndef QMD_ASSERT
+#define QMD_ASSERT(expression, fmt, ...) do {				\
+	if (!(expression))						\
+		QMD_PANIC("%s:%u: %s: " fmt,				\
+		    __FILE__, __LINE__, __func__,  ##__VA_ARGS__);	\
+} while (0)
+#endif /* !QMD_ASSERT */
+#else /* !QUEUE_MACRO_DEBUG_ASSERTIONS */
+#undef QMD_ASSERT
+#define QMD_ASSERT(test, fmt, ...) do {} while (0)
+#endif /* QUEUE_MACRO_DEBUG_ASSERTIONS */
+
+
 #ifdef __cplusplus
 /*
  * In C++ there can be structure lists and class lists:
@@ -176,6 +231,7 @@ struct qm_trace {
 /*
  * Singly-linked List declarations.
  */
+
 #define SLIST_HEAD(name, type)						\
 struct name {								\
 	struct type *slh_first;	/* first element */			\
@@ -202,27 +258,19 @@ struct {								\
 /*
  * Singly-linked List functions.
  */
-#if (defined(_KERNEL) && defined(INVARIANTS))
-#define QMD_SLIST_CHECK_PREVPTR(prevp, elm) do {			\
-	if (*(prevp) != (elm))						\
-		panic("Bad prevptr *(%p) == %p != %p",			\
-		    (prevp), *(prevp), (elm));				\
-} while (0)
 
-#define SLIST_ASSERT_EMPTY(head) do {					\
-	if (!SLIST_EMPTY((head)))					\
-		panic("%s: slist %p is not empty", __func__, (head));	\
-} while (0)
+#define QMD_SLIST_CHECK_PREVPTR(prevp, elm)				\
+	QMD_ASSERT(*(prevp) == (elm),					\
+	    "Bad prevptr *(%p) == %p != %p",				\
+	    (prevp), *(prevp), (elm))
 
-#define SLIST_ASSERT_NONEMPTY(head) do {				\
-	if (SLIST_EMPTY((head)))					\
-		panic("%s: slist %p is empty", __func__, (head));	\
-} while (0)
-#else
-#define QMD_SLIST_CHECK_PREVPTR(prevp, elm)
-#define SLIST_ASSERT_EMPTY(head)
-#define SLIST_ASSERT_NONEMPTY(head)
-#endif
+#define SLIST_ASSERT_EMPTY(head)					\
+	QMD_ASSERT(SLIST_EMPTY((head)),					\
+	    "slist %p is not empty", (head))
+
+#define SLIST_ASSERT_NONEMPTY(head)					\
+	QMD_ASSERT(!SLIST_EMPTY((head)),				\
+	    "slist %p is empty", (head))
 
 #define SLIST_CONCAT(head1, head2, type, field) do {			\
 	QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head1);		\
@@ -333,6 +381,7 @@ struct {								\
 /*
  * Singly-linked Tail queue declarations.
  */
+
 #define STAILQ_HEAD(name, type)						\
 struct name {								\
 	struct type *stqh_first;/* first element */			\
@@ -361,46 +410,36 @@ struct {								\
 /*
  * Singly-linked Tail queue functions.
  */
-#if (defined(_KERNEL) && defined(INVARIANTS))
+
 /*
  * QMD_STAILQ_CHECK_EMPTY(STAILQ_HEAD *head)
  *
  * Validates that the stailq head's pointer to the last element's next pointer
  * actually points to the head's first element pointer field.
  */
-#define QMD_STAILQ_CHECK_EMPTY(head) do {				\
-	if ((head)->stqh_last != &(head)->stqh_first)			\
-		panic("Empty stailq %p->stqh_last is %p, not head's "	\
-		    "first field address", (head), (head)->stqh_last);	\
-} while (0)
+#define QMD_STAILQ_CHECK_EMPTY(head)					\
+	QMD_ASSERT((head)->stqh_last == &(head)->stqh_first,		\
+	    "Empty stailq %p->stqh_last is %p, "			\
+	    "not head's first field address",				\
+	    (head), (head)->stqh_last)
 
 /*
  * QMD_STAILQ_CHECK_TAIL(STAILQ_HEAD *head)
  *
  * Validates that the stailq's last element's next pointer is NULL.
  */
-#define QMD_STAILQ_CHECK_TAIL(head) do {				\
-	if (*(head)->stqh_last != NULL)					\
-		panic("Stailq %p last element's next pointer is %p, "	\
-		    "not NULL", (head), *(head)->stqh_last);		\
-} while (0)
-
-#define STAILQ_ASSERT_EMPTY(head) do {					\
-	if (!STAILQ_EMPTY((head)))					\
-		panic("%s: stailq %p is not empty", __func__, (head));	\
-} while (0)
+#define QMD_STAILQ_CHECK_TAIL(head)					\
+	QMD_ASSERT(*(head)->stqh_last == NULL,				\
+	    "Stailq %p last element's next pointer is "			\
+	    "%p, not NULL", (head), *(head)->stqh_last)
 
-#define STAILQ_ASSERT_NONEMPTY(head) do {				\
-	if (STAILQ_EMPTY((head)))					\
-		panic("%s: stailq %p is empty", __func__, (head));	\
-} while (0)
+#define STAILQ_ASSERT_EMPTY(head)					\
+	QMD_ASSERT(STAILQ_EMPTY((head)),				\
+	    "stailq %p is not empty", (head))
 
-#else
-#define QMD_STAILQ_CHECK_EMPTY(head)
-#define QMD_STAILQ_CHECK_TAIL(head)
-#define STAILQ_ASSERT_EMPTY(head)
-#define STAILQ_ASSERT_NONEMPTY(head)
-#endif /* _KERNEL && INVARIANTS */
+#define STAILQ_ASSERT_NONEMPTY(head)					\
+	QMD_ASSERT(!STAILQ_EMPTY((head)),				\
+	    "stailq %p is empty", (head))
 
 #define STAILQ_CONCAT(head1, head2) do {				\
 	if (!STAILQ_EMPTY((head2))) {					\
@@ -531,6 +570,7 @@ struct {								\
 /*
  * List declarations.
  */
+
 #define LIST_HEAD(name, type)						\
 struct name {								\
 	struct type *lh_first;	/* first element */			\
@@ -560,19 +600,18 @@ struct {								\
  * List functions.
  */
 
-#if (defined(_KERNEL) && defined(INVARIANTS))
 /*
  * QMD_LIST_CHECK_HEAD(LIST_HEAD *head, LIST_ENTRY NAME)
  *
  * If the list is non-empty, validates that the first element of the list
  * points back at 'head.'
  */
-#define QMD_LIST_CHECK_HEAD(head, field) do {				\
-	if (LIST_FIRST((head)) != NULL &&				\
-	    LIST_FIRST((head))->field.le_prev !=			\
-	     &LIST_FIRST((head)))					\
-		panic("Bad list head %p first->prev != head", (head));	\
-} while (0)
+#define QMD_LIST_CHECK_HEAD(head, field)				\
+	QMD_ASSERT(LIST_FIRST((head)) == NULL ||			\
+	    LIST_FIRST((head))->field.le_prev ==			\
+	    &LIST_FIRST((head)),					\
+	    "Bad list head %p first->prev != head",			\
+	    (head))
 
 /*
  * QMD_LIST_CHECK_NEXT(TYPE *elm, LIST_ENTRY NAME)
@@ -580,39 +619,28 @@ struct {								\
  * If an element follows 'elm' in the list, validates that the next element
  * points back at 'elm.'
  */
-#define QMD_LIST_CHECK_NEXT(elm, field) do {				\
-	if (LIST_NEXT((elm), field) != NULL &&				\
-	    LIST_NEXT((elm), field)->field.le_prev !=			\
-	     &((elm)->field.le_next))					\
-		panic("Bad link elm %p next->prev != elm", (elm));	\
-} while (0)
+#define QMD_LIST_CHECK_NEXT(elm, field)					\
+	QMD_ASSERT(LIST_NEXT((elm), field) == NULL ||			\
+	    LIST_NEXT((elm), field)->field.le_prev ==			\
+	    &((elm)->field.le_next),					\
+	    "Bad link elm %p next->prev != elm", (elm))
 
 /*
  * QMD_LIST_CHECK_PREV(TYPE *elm, LIST_ENTRY NAME)
  *
  * Validates that the previous element (or head of the list) points to 'elm.'
  */
-#define QMD_LIST_CHECK_PREV(elm, field) do {				\
-	if (*(elm)->field.le_prev != (elm))				\
-		panic("Bad link elm %p prev->next != elm", (elm));	\
-} while (0)
+#define QMD_LIST_CHECK_PREV(elm, field)					\
+	QMD_ASSERT(*(elm)->field.le_prev == (elm),			\
+	    "Bad link elm %p prev->next != elm", (elm))
 
-#define LIST_ASSERT_EMPTY(head) do {					\
-	if (!LIST_EMPTY((head)))					\
-		panic("%s: list %p is not empty", __func__, (head));	\
-} while (0)
+#define LIST_ASSERT_EMPTY(head)						\
+	QMD_ASSERT(LIST_EMPTY((head)),					\
+	    "list %p is not empty", (head))
 
-#define LIST_ASSERT_NONEMPTY(head) do {					\
-	if (LIST_EMPTY((head)))						\
-		panic("%s: list %p is empty", __func__, (head));	\
-} while (0)
-#else
-#define QMD_LIST_CHECK_HEAD(head, field)
-#define QMD_LIST_CHECK_NEXT(elm, field)
-#define QMD_LIST_CHECK_PREV(elm, field)
-#define LIST_ASSERT_EMPTY(head)
-#define LIST_ASSERT_NONEMPTY(head)
-#endif /* (_KERNEL && INVARIANTS) */
+#define LIST_ASSERT_NONEMPTY(head)					\
+	QMD_ASSERT(!LIST_EMPTY((head)),					\
+	    "list %p is empty", (head))
 
 #define LIST_CONCAT(head1, head2, type, field) do {			\
 	QUEUE_TYPEOF(type) *curelm = LIST_FIRST(head1);			\
@@ -753,6 +781,7 @@ struct {								\
 /*
  * Tail queue declarations.
  */
+
 #define TAILQ_HEAD(name, type)						\
 struct name {								\
 	struct type *tqh_first;	/* first element */			\
@@ -787,29 +816,29 @@ struct {								\
 /*
  * Tail queue functions.
  */
-#if (defined(_KERNEL) && defined(INVARIANTS))
+
 /*
  * QMD_TAILQ_CHECK_HEAD(TAILQ_HEAD *head, TAILQ_ENTRY NAME)
  *
  * If the tailq is non-empty, validates that the first element of the tailq
  * points back at 'head.'
  */
-#define QMD_TAILQ_CHECK_HEAD(head, field) do {				\
-	if (!TAILQ_EMPTY(head) &&					\
-	    TAILQ_FIRST((head))->field.tqe_prev !=			\
-	     &TAILQ_FIRST((head)))					\
-		panic("Bad tailq head %p first->prev != head", (head));	\
-} while (0)
+#define QMD_TAILQ_CHECK_HEAD(head, field)				\
+	QMD_ASSERT(TAILQ_EMPTY(head) ||					\
+	    TAILQ_FIRST((head))->field.tqe_prev ==			\
+	    &TAILQ_FIRST((head)),					\
+	    "Bad tailq head %p first->prev != head",			\
+	    (head))
 
 /*
  * QMD_TAILQ_CHECK_TAIL(TAILQ_HEAD *head, TAILQ_ENTRY NAME)
  *
  * Validates that the tail of the tailq is a pointer to pointer to NULL.
  */
-#define QMD_TAILQ_CHECK_TAIL(head, field) do {				\
-	if (*(head)->tqh_last != NULL)					\
-		panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head));	\
-} while (0)
+#define QMD_TAILQ_CHECK_TAIL(head, field)				\
+	QMD_ASSERT(*(head)->tqh_last == NULL,				\
+	    "Bad tailq NEXT(%p->tqh_last) != NULL",			\
+	    (head))
 
 /*
  * QMD_TAILQ_CHECK_NEXT(TYPE *elm, TAILQ_ENTRY NAME)
@@ -817,40 +846,28 @@ struct {								\
  * If an element follows 'elm' in the tailq, validates that the next element
  * points back at 'elm.'
  */
-#define QMD_TAILQ_CHECK_NEXT(elm, field) do {				\
-	if (TAILQ_NEXT((elm), field) != NULL &&				\
-	    TAILQ_NEXT((elm), field)->field.tqe_prev !=			\
-	     &((elm)->field.tqe_next))					\
-		panic("Bad link elm %p next->prev != elm", (elm));	\
-} while (0)
+#define QMD_TAILQ_CHECK_NEXT(elm, field)				\
+	QMD_ASSERT(TAILQ_NEXT((elm), field) == NULL ||			\
+	    TAILQ_NEXT((elm), field)->field.tqe_prev ==			\
+	    &((elm)->field.tqe_next),					\
+	    "Bad link elm %p next->prev != elm", (elm))
 
 /*
  * QMD_TAILQ_CHECK_PREV(TYPE *elm, TAILQ_ENTRY NAME)
  *
  * Validates that the previous element (or head of the tailq) points to 'elm.'
  */
-#define QMD_TAILQ_CHECK_PREV(elm, field) do {				\
-	if (*(elm)->field.tqe_prev != (elm))				\
-		panic("Bad link elm %p prev->next != elm", (elm));	\
-} while (0)
+#define QMD_TAILQ_CHECK_PREV(elm, field)				\
+	QMD_ASSERT(*(elm)->field.tqe_prev == (elm),			\
+	    "Bad link elm %p prev->next != elm", (elm))
 
-#define TAILQ_ASSERT_EMPTY(head) do {					\
-	if (!TAILQ_EMPTY((head)))					\
-		panic("%s: tailq %p is not empty", __func__, (head));	\
-} while (0)
+#define TAILQ_ASSERT_EMPTY(head)					\
+	QMD_ASSERT(TAILQ_EMPTY((head)),					\
+	    "tailq %p is not empty", (head))
 
-#define TAILQ_ASSERT_NONEMPTY(head) do {				\
-	if (TAILQ_EMPTY((head)))					\
-		panic("%s: tailq %p is empty", __func__, (head));	\
-} while (0)
-#else
-#define QMD_TAILQ_CHECK_HEAD(head, field)
-#define QMD_TAILQ_CHECK_TAIL(head, headname)
-#define QMD_TAILQ_CHECK_NEXT(elm, field)
-#define QMD_TAILQ_CHECK_PREV(elm, field)
-#define TAILQ_ASSERT_EMPTY(head)
-#define TAILQ_ASSERT_NONEMPTY(head)
-#endif /* (_KERNEL && INVARIANTS) */
+#define TAILQ_ASSERT_NONEMPTY(head)					\
+	QMD_ASSERT(!TAILQ_EMPTY((head)),				\
+	    "tailq %p is empty", (head))
 
 #define TAILQ_CONCAT(head1, head2, field) do {				\
 	if (!TAILQ_EMPTY(head2)) {					\