ドライバコードのデバッグは、次の理由から、ユーザープログラムの場合より困難です。
ドライバはハードウェアと直接対話する
ドライバは、ユーザープロセスに提供されるオペレーティングシステムの保護なしに動作する
ドライバにはデバッグのサポートを組み込むようにします。このサポートは、保守作業と将来の開発を促進します。
それぞれの関数、データ要素、およびドライバプリプロセッサの定義は、ドライバごとに一意であることが必要です。
ドライバモジュールはカーネルにリンクされます。特定のドライバに対して一意である各シンボルの名前は、決してほかのカーネルシンボルと衝突しないようにしてください。そのような衝突を回避するには、特定のドライバの関数とデータ要素のそれぞれに、そのドライバに共通の接頭辞を使用して名前を付ける必要があります。接頭辞を使用すると、各ドライバシンボルに一意の名前を付けるのに十分なはずです。通常、この接頭辞はドライバの名前またはドライバ名の略語になります。たとえば xx_open() は、ドライバ xx の open (9E) ルーチンの名前です。
ドライバを作成するときには、ドライバに必ずいくつかのシステムヘッダーファイルを含める必要があります。これらのヘッダーファイル内に記載された、グローバルに認識される名前を予測することはできません。これらの名前との衝突を避けるには、識別用の接頭辞を使用して、各ドライバプリプロセッサの定義に一意の名前を付ける必要があります。
ドライバシンボルの接頭辞を識別できれば、トラブルシューティング時にシステムログやパニックを解読する助けにもなります。あいまいな attach() 関数に関連するエラーを確認する代わりに、xx_attach() に関するエラーメッセージを確認します。
cmn_err(9F) 関数を使用して、デバイスドライバ内からシステムログにメッセージを出力します。カーネルモジュール用の cmn_err (9F) 関数はアプリケーション用の printf (3C) 関数と似ています。 cmn_err(9F) 関数には、デバイスレジスタのビットを出力する %b 書式などの追加の書式文字が用意されています。cmn_err (9F) 関数はシステムログにメッセージを書き込みます。tail(1) コマンドを使用すると、/var/adm/messages へのこれらのメッセージをモニターできます。
% tail -f /var/adm/messages
アサーションは非常に役立つ形式のアクティブドキュメントです。ASSERT(9F) の構文は次のとおりです。
void ASSERT(EXPRESSION)
ASSERT() マクロは、真であることが予期されている条件が実際には偽である場合、カーネルの実行を停止します。ASSERT() はプログラマに、特定のコードによって作成された前提条件を検証する方法を提供します。
ASSERT() マクロが定義されるのは、DEBUG コンパイルシンボルが定義されている場合のみです。DEBUG が定義されていない場合、ASSERT() マクロは有効になりません。
次のアサーションの例では、特定のポインタの値は NULL ではないという前提条件をテストしています。
ASSERT(ptr != NULL);
ドライバが DEBUG を使用してコンパイルされていて、実行のこの時点で ptr の値が NULL である場合、次のパニックメッセージがコンソールに出力されます。
panic: assertion failed: ptr != NULL, file: driver.c, line: 56
mutex_owned(9F) の構文は次のとおりです。
int mutex_owned(kmutex_t *mp);
ドライバ開発のかなりの部分で、複数のスレッドを正しく処理する必要があります。mutex が取得されるときには常にコメントを使用する必要があります。明らかに必要な mutex が取得されていない場合は、コメントがさらに役立つことがあります。mutex がスレッドによって保持されているかどうかを判定するには、ASSERT(9F) 内で mutex_owned() を使用します。
void helper(void) { /* this routine should always be called with xsp's mutex held */ ASSERT(mutex_owned(&xsp->mu)); /* ... */ }
条件付きコンパイルによってドライバ内にデバッグ用のコードを挿入するには、DEBUG などのプリプロセッサシンボル、またはグローバル変数を使用します。条件付きコンパイルを使用すると、本番ドライバ内で不要なコードを削除できます。実行時のデバッグの出力量を設定するには変数を使用します。出力は、ioctl またはデバッガを使用して実行時のデバッグレベルを設定することで指定できます。通常は、これらの 2 つの方法が組み合わせられます。
次の例はコンパイラを使って到達不能コード (この場合は常に偽となるゼロのテストに続くコード) を削除しています。この例では、/etc/system で設定したり、デバッガによってパッチを適用したりできるローカル変数も提供しています。
#ifdef DEBUG /* comments on values of xxdebug and what they do */ static int xxdebug; #define dcmn_err if (xxdebug) cmn_err #else #define dcmn_err if (0) cmn_err #endif /* ... */ dcmn_err(CE_NOTE, "Error!\n");
この方法は、cmn_err(9F) が可変数の引数を持つ状況を処理します。もう 1 つの方法は、マクロには引数が 1 つ (cmn_err(9F) 用の括弧付きの引数リスト) あることを利用します。マクロはこの引数を削除します。このマクロは、DEBUG が定義されていない場合はマクロをゼロに展開することによって、オプティマイザへの依存も削除します。
#ifdef DEBUG /* comments on values of xxdebug and what they do */ static int xxdebug; #define dcmn_err(X) if (xxdebug) cmn_err X #else #define dcmn_err(X) /* nothing */ #endif /* ... */ /* Note:double parentheses are required when using dcmn_err. */ dcmn_err((CE_NOTE, "Error!"));
この手法は多くの方法で拡張できます。1 つの方法として、xxdebug の値に応じて、cmn_err(9F) からのさまざまなメッセージを指定します。ただし、そのような場合は、デバッグ情報が多すぎてコードが複雑でわかりにくくならないように気を付ける必要があります。
もう 1 つ一般的なのは、xxlog() 関数を記述する方法です。この関数は vsprintf(9F) または vcmn_err(9F) を使用して可変引数リストを処理します。