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
This commit is contained in:
Olivier Certner 2025-04-04 21:51:43 +02:00
parent 0f2090ccfe
commit 1c5fea9e8b
No known key found for this signature in database
GPG key ID: 8CA13040971E2627
2 changed files with 200 additions and 145 deletions

View file

@ -1390,18 +1390,80 @@ while (n1 != NULL) {
TAILQ_INIT(&head);
.Ed
.Sh DIAGNOSTICS
When debugging
.Nm queue(3) ,
it can be useful to trace queue changes.
.Nm queue(3)
provides several diagnostic and debugging facilities.
.Pp
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
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 element
.Fa elm ,
which must directly follow the element whose
.Va &SLIST_NEXT()
is
.Fa prev ,
from the list.
This macro may insert, under conditions detailed above, check code that
validates that
.Fa elm
indeed follows
.Fa prev
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
at compile time.
.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
@ -1409,30 +1471,6 @@ returns true if
has been trashed by the
.Va QUEUE_MACRO_DEBUG_TRASH
option.
.Pp
In the kernel (with
.Va INVARIANTS
enabled), 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
.Fa elm ,
which must directly follow the element whose
.Va &SLIST_NEXT()
is
.Fa prev ,
from the SLIST.
This macro validates that
.Fa elm
follows
.Fa prev
in
.Va INVARIANTS
mode.
.El
.Sh SEE ALSO
.Xr arb 3 ,
.Xr tree 3

View file

@ -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 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_EMPTY(head) do { \
if (!STAILQ_EMPTY((head))) \
panic("%s: stailq %p is not empty", __func__, (head)); \
} while (0)
#define STAILQ_ASSERT_EMPTY(head) \
QMD_ASSERT(STAILQ_EMPTY((head)), \
"stailq %p is not empty", (head))
#define STAILQ_ASSERT_NONEMPTY(head) do { \
if (STAILQ_EMPTY((head))) \
panic("%s: stailq %p is empty", __func__, (head)); \
} while (0)
#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)) { \