Debugging Preparation Techniques

The driver operates without the protection of the operating
system that is available to user processes

Be sure to build debugging support
into your driver. This support facilitates both maintenance work and future
development.

Use a Unique Prefix to Avoid Kernel Symbol Collisions

The name of each function, data element, and driver preprocessor definition
must be unique for each driver.

A driver module is linked into the kernel. The name of each symbol unique
to a particular driver must not collide with other kernel symbols. To avoid
such collisions, each function and data element for a particular driver must
be named with a prefix common to that driver. The prefix must be sufficient
to uniquely name each driver symbol. Typically, this prefix is the name of
the driver or an abbreviation for the name of the driver. For example, xx_open() would be the name of the open(9E) routine of driver xx.

When building a driver, a driver must necessarily include a number of
system header files. The globally-visible names within these header files
cannot be predicted. To avoid collisions with these names, each driver preprocessor
definition must be given a unique name by using an identifying prefix.

A distinguishing driver symbol prefix also is an aid to deciphering
system logs and panics when troubleshooting. Instead of seeing an error related
to an ambiguous attach() function, you see an error message
about xx_attach().

Use cmn_err() to Log Driver
Activity

Use the cmn_err(9F) function to print messages
to a system log from within the device driver. The cmn_err(9F) function for kernel
modules is similar to the printf(3C) function for applications. The cmn_err(9F) function
provides additional format characters, such as the %b format
to print device register bits. The cmn_err(9F) function writes messages to a system
log. Use the tail(1) command
to monitor these messages on /var/adm/messages.

% tail -f /var/adm/messages

Use ASSERT() to Catch Invalid
Assumptions

Assertions are an extremely valuable form of active documentation. The
syntax for
ASSERT(9F) is
as follows:

void ASSERT(EXPRESSION)

The ASSERT() macro halts the execution of the kernel
if a condition that is expected to be true is actually false.
ASSERT() provides a way for the programmer to validate the
assumptions made by a piece of code.

The ASSERT() macro is defined only when the
DEBUG compilation symbol is defined. When
DEBUG is not defined, the ASSERT() macro
has no effect.

The following example assertion tests the assumption that a particular
pointer value is not NULL:

ASSERT(ptr != NULL);

If the driver has been compiled with DEBUG, and if
the value of ptr is NULL at this point
in execution, then the following panic message is printed to the console:

panic: assertion failed: ptr != NULL, file: driver.c, line: 56

Note –

Because ASSERT(9F) uses the DEBUG compilation
symbol, any conditional debugging code should also use DEBUG.

Use mutex_owned() to Validate
and Document Locking Requirements

A significant portion of driver
development involves properly handling multiple threads. Comments should always
be used when a mutex is acquired. Comments can be even more useful when an
apparently necessary mutex is not acquired. To determine
whether a mutex is held by a thread, use mutex_owned() within ASSERT(9F):

void helper(void)
{
/* this routine should always be called with xsp's mutex held */
ASSERT(mutex_owned(&xsp->mu));
/* ... */
}

Note –

mutex_owned() is only valid within ASSERT() macros. You should use mutex_owned() to control
the behavior of a driver.

Use Conditional Compilation to
Toggle Costly Debugging Features

You can insert code for debugging into a driver through conditional
compiles by using a preprocessor symbol such as DEBUG or
by using a global variable. With conditional compilation, unnecessary code
can be removed in the production driver. Use a variable to set the amount
of debugging output at runtime. The output can be specified by setting a debugging
level at runtime with an ioctl or through a debugger. Commonly,
these two methods are combined.

The following example relies on the compiler to remove unreachable code,
in this case, the code following the always-false test of zero. The example
also provides a local variable that can be set in /etc/system or
patched by a debugger.

This method handles the fact that cmn_err(9F) has a variable number of
arguments. Another method relies on the fact that the macro has one argument,
a parenthesized argument list for cmn_err(9F). The macro removes this argument.
This macro also removes the reliance on the optimizer by expanding the macro
to nothing if DEBUG is not defined.

You can extend this technique in many ways. One way is to specify different
messages from cmn_err(9F),
depending on the value of xxdebug. However, in such a case,
you must be careful not to obscure the code with too much debugging information.

Another common scheme is to write an xxlog() function,
which uses vsprintf(9F) or vcmn_err(9F) to handle
variable argument lists.