diff --git a/share/man/man3/queue.3 b/share/man/man3/queue.3 --- a/share/man/man3/queue.3 +++ b/share/man/man3/queue.3 @@ -1390,18 +1390,74 @@ 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 +Debug 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 debug 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 debug 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 a 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 . +.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 debug 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 +1465,6 @@ 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 diff --git a/sys/sys/queue.h b/sys/sys/queue.h --- 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,51 @@ #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 a default definition of QMD_PANIC() + * if not already defined. + */ +#if defined(QUEUE_MACRO_DEBUG_ASSERTIONS) && !defined(QMD_PANIC) +#if defined(_KERNEL) || defined(_STANDALONE) +/* + * On _STANDALONE, either or the headers using provide + * a declaration or macro for panic(). + */ +#ifdef _KERNEL +#include +#endif +#define QMD_PANIC(fmt, ...) do { \ + panic("File '%s', line %u: " fmt, \ + __FILE__, __LINE__, ##__VA_ARGS__); \ +} while (0) +#else /* !(_KERNEL || _STANDALONE) */ +#include +#include +#define QMD_PANIC(fmt, ...) do { \ + fprintf(stderr, "File '%s', line %u: " fmt "\n", \ + __FILE__, __LINE__, ##__VA_ARGS__); \ + abort(); \ +} while (0) +#endif /* _KERNEL || _STANDALONE */ +#endif /* QUEUE_MACRO_DEBUG_ASSERTIONS && !QMD_PANIC */ + + #ifdef __cplusplus /* * In C++ there can be structure lists and class lists: @@ -202,31 +246,27 @@ /* * Singly-linked List functions. */ -#if (defined(_KERNEL) && defined(INVARIANTS)) - +#ifdef QUEUE_MACRO_DEBUG_ASSERTIONS #define QMD_SLIST_CHECK_PREVPTR(prevp, elm) do { \ if (*(prevp) != (elm)) \ - panic("Bad prevptr *(%p) == %p != %p", \ + QMD_PANIC("Bad prevptr *(%p) == %p != %p", \ (prevp), *(prevp), (elm)); \ } while (0) #define SLIST_ASSERT_EMPTY(head) do { \ if (!SLIST_EMPTY((head))) \ - panic("slist %p is not empty", (head)); \ + QMD_PANIC("slist %p is not empty", (head)); \ } while (0) #define SLIST_ASSERT_NONEMPTY(head) do { \ if (SLIST_EMPTY((head))) \ - panic("slist %p is empty", (head)); \ + QMD_PANIC("slist %p is empty", (head)); \ } while (0) - #else #define QMD_SLIST_CHECK_PREVPTR(prevp, elm) #define SLIST_ASSERT_EMPTY(head) #define SLIST_ASSERT_NONEMPTY(head) #endif - - #define SLIST_CONCAT(head1, head2, type, field) do { \ QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head1); \ if (curelm == NULL) { \ @@ -364,7 +404,7 @@ /* * Singly-linked Tail queue functions. */ -#if (defined(_KERNEL) && defined(INVARIANTS)) +#ifdef QUEUE_MACRO_DEBUG_ASSERTIONS /* * QMD_STAILQ_CHECK_EMPTY(STAILQ_HEAD *head) * @@ -373,8 +413,9 @@ */ #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); \ + QMD_PANIC("Empty stailq %p->stqh_last is %p, " \ + "not head's first field address", \ + (head), (head)->stqh_last); \ } while (0) /* @@ -384,18 +425,18 @@ */ #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); \ + QMD_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("stailq %p is not empty", (head)); \ + QMD_PANIC("stailq %p is not empty", (head)); \ } while (0) #define STAILQ_ASSERT_NONEMPTY(head) do { \ if (STAILQ_EMPTY((head))) \ - panic("stailq %p is empty", (head)); \ + QMD_PANIC("stailq %p is empty", (head)); \ } while (0) #else @@ -403,7 +444,7 @@ #define QMD_STAILQ_CHECK_TAIL(head) #define STAILQ_ASSERT_EMPTY(head) #define STAILQ_ASSERT_NONEMPTY(head) -#endif /* _KERNEL && INVARIANTS */ +#endif /* QUEUE_MACRO_DEBUG_ASSERTIONS */ #define STAILQ_CONCAT(head1, head2) do { \ if (!STAILQ_EMPTY((head2))) { \ @@ -563,7 +604,7 @@ * List functions. */ -#if (defined(_KERNEL) && defined(INVARIANTS)) +#ifdef QUEUE_MACRO_DEBUG_ASSERTIONS /* * QMD_LIST_CHECK_HEAD(LIST_HEAD *head, LIST_ENTRY NAME) * @@ -574,7 +615,8 @@ if (LIST_FIRST((head)) != NULL && \ LIST_FIRST((head))->field.le_prev != \ &LIST_FIRST((head))) \ - panic("Bad list head %p first->prev != head", (head)); \ + QMD_PANIC("Bad list head %p first->prev != head", \ + (head)); \ } while (0) /* @@ -587,7 +629,7 @@ 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)); \ + QMD_PANIC("Bad link elm %p next->prev != elm", (elm)); \ } while (0) /* @@ -597,17 +639,17 @@ */ #define QMD_LIST_CHECK_PREV(elm, field) do { \ if (*(elm)->field.le_prev != (elm)) \ - panic("Bad link elm %p prev->next != elm", (elm)); \ + QMD_PANIC("Bad link elm %p prev->next != elm", (elm)); \ } while (0) #define LIST_ASSERT_EMPTY(head) do { \ if (!LIST_EMPTY((head))) \ - panic("list %p is not empty", (head)); \ + QMD_PANIC("list %p is not empty", (head)); \ } while (0) #define LIST_ASSERT_NONEMPTY(head) do { \ if (LIST_EMPTY((head))) \ - panic("list %p is empty", (head)); \ + QMD_PANIC("list %p is empty", (head)); \ } while (0) #else @@ -616,8 +658,7 @@ #define QMD_LIST_CHECK_PREV(elm, field) #define LIST_ASSERT_EMPTY(head) #define LIST_ASSERT_NONEMPTY(head) -#endif /* (_KERNEL && INVARIANTS) */ - +#endif /* QUEUE_MACRO_DEBUG_ASSERTIONS */ #define LIST_CONCAT(head1, head2, type, field) do { \ QUEUE_TYPEOF(type) *curelm = LIST_FIRST(head1); \ if (curelm == NULL) { \ @@ -791,7 +832,7 @@ /* * Tail queue functions. */ -#if (defined(_KERNEL) && defined(INVARIANTS)) +#ifdef QUEUE_MACRO_DEBUG_ASSERTIONS /* * QMD_TAILQ_CHECK_HEAD(TAILQ_HEAD *head, TAILQ_ENTRY NAME) * @@ -802,7 +843,8 @@ if (!TAILQ_EMPTY(head) && \ TAILQ_FIRST((head))->field.tqe_prev != \ &TAILQ_FIRST((head))) \ - panic("Bad tailq head %p first->prev != head", (head)); \ + QMD_PANIC("Bad tailq head %p first->prev != head", \ + (head)); \ } while (0) /* @@ -812,7 +854,8 @@ */ #define QMD_TAILQ_CHECK_TAIL(head, field) do { \ if (*(head)->tqh_last != NULL) \ - panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \ + QMD_PANIC("Bad tailq NEXT(%p->tqh_last) != NULL", \ + (head)); \ } while (0) /* @@ -825,7 +868,7 @@ 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)); \ + QMD_PANIC("Bad link elm %p next->prev != elm", (elm)); \ } while (0) /* @@ -835,17 +878,17 @@ */ #define QMD_TAILQ_CHECK_PREV(elm, field) do { \ if (*(elm)->field.tqe_prev != (elm)) \ - panic("Bad link elm %p prev->next != elm", (elm)); \ + QMD_PANIC("Bad link elm %p prev->next != elm", (elm)); \ } while (0) #define TAILQ_ASSERT_EMPTY(head) do { \ if (!TAILQ_EMPTY((head))) \ - panic("tailq %p is not empty", (head)); \ + QMD_PANIC("tailq %p is not empty", (head)); \ } while (0) #define TAILQ_ASSERT_NONEMPTY(head) do { \ if (TAILQ_EMPTY((head))) \ - panic("tailq %p is empty", (head)); \ + QMD_PANIC("tailq %p is empty", (head)); \ } while (0) #else @@ -855,7 +898,7 @@ #define QMD_TAILQ_CHECK_PREV(elm, field) #define TAILQ_ASSERT_EMPTY(head) #define TAILQ_ASSERT_NONEMPTY(head) -#endif /* (_KERNEL && INVARIANTS) */ +#endif /* QUEUE_MACRO_DEBUG_ASSERTIONS */ #define TAILQ_CONCAT(head1, head2, field) do { \ if (!TAILQ_EMPTY(head2)) { \