Windows Device Driver Programming Part 1

Last modified: 2005/04/24 15:00:01

はじめに

日本語サイトでデバイスドライバ作成を解説しているページがありません。どんなに探してもありません。そんなにデバイスドライバって需要ないのですか? そんなものですか? 個人的にはかなり興味あるんですが。ということで、今回はデバイスドライバ作成についてやっていきたいと思います。ここでひとつ断っておきますが、私はデバイスドライバに関してはほぼ知識ゼロです。全然知りません。全然分かりません。これは本当です。ちょっとテキストの誤植を大目にみてもらおう思って書いておく保険じゃありません。なので、「分かりやすく」や「内容に間違いなく」といったことが保障できません。またデバイスドライバはカーネルモードで動作するためWindowsに致命的なダメージを与える可能性があります。今回、私はWindowsXP + WinXPDDKで解説していますが、ブルースクリーンなんて当たり前です。日常茶飯事です。なので、場合によっては「Windowsが起動しなくなりました」ということが有り得るかもしれません。でも、そこは貴方のスキルでカバーしてください。決して私には頼らないでください。頼られてもたぶん分かりません。ごめんなさい。もう信じるものは己(おのれ)のみです。自分のスキルがすべてです。それだけでなんとか生き抜いてください。すみません、ちょっと大げさでした。でも、どっちにしろデバイスドライバを作ろうとか思ってる兵(つわもの)は自分のスキルしか信じてない人が多い気がします。あくまで気がするだけですが。なんだかいつになくグダグダな「はじめに」ですみません(^^;。今回実験を行った私の環境は「WindowsXP」で、コンパイラは「WinDDK XP版」です。では、さっそく始めることにしましょう。ようこそカーネルモードの世界へ。

コンパイル

さっそくデバイスドライバを作成してみることにしますが、やはりデバイスドライバというだけあって、コンパイルから実行方法までややこしいことが多いです。よって、最初はソースコードを書くよりもWindowsシステムに関していろいろとイジることの方が多くなります。その辺はどうかご了承ください。といってるそばからデバイスドライバのソースコードを見てもらうわけなのですが(^^;。hello.cppはもっともシンプルなデバイスドライバのソースコードです。

-----  hello.cpp
#ifdef __cplusplus
extern "C" {
#endif

#include <wdm.h>

NTSTATUS DriverEntry(PDRIVER_OBJECT, PUNICODE_STRING);

#ifdef __cplusplus
}
#endif

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
                     IN PUNICODE_STRING pRegistryPath)
{
    return STATUS_SUCCESS;
}
-----

まぁ見て分かる通り何もしないプログラムのようです。デバイスドライバでは、DriverEntryからコードが始まります。要するにWinMainもしくはmain関数みたいなものです。では、これをコンパイルして実行してください。といっても、どうやってコンパイルするか分かりません。そもそもWinDDKをもってませんという人は以下のアドレスからDLしてください。私はWindowsXP環境なので「XP DDK」を使っています。CD-ROMイメージなのでCDに焼くか、仮想CDドライブソフト(Daemon Toolsなど)を使ってインストールしてください。リンク切れてたらマイクロソフトから注文してください。

DDK:
http://club.shelek.com/viewfiles.php?id=2

DAEMON Tools:
http://www.daemon-tools.cc/dtcc/portal/download.php?mode=ViewCategory&catid=5

DAEMON Tools Homepage:
http://www.daemon-tools.cc/dtcc/portal/index.php

Microsoft Windows Driver Development Kit:
http://www.microsoft.com/whdc/devtools/ddk/default.mspx

2年くらい前まではマイクロソフトのホームページで無償で提供されていたのですが、何故か突然止めてしまったので、現在では正規の入手方法はマイクロソフトに直接注文するしかなくなりました。ちなみにDDK本体は無料なのですが、送料が25ドルくらいかかるらしいです。でも、ネット上を探してみれば意外と見つかりますので、わざわざ注文する必要も無いかもしれません。

----- 追記(2006/02/27):
いつの間にやらマイクロソフトがDDKを公開しており、以下のマイクロソフトのページからWinDDK2003(2000/XP版も同封)が入ったカーネルモードドライバフレームワークをダウンロードすることができます。上記のリンクも一応残しておきますが、新たにダウンロードする場合はマイクロソフトのページから行った方がよいでしょう。(seiさん、情報有難うございましたm(_ _)m)
http://www.microsoft.com/whdc/devtools/wdk/betawdk.mspx
http://www.microsoft.com/japan/whdc/driver/wdf/KMDF_pkg.mspx
http://www.microsoft.com/japan/whdc/driver/WDF/KMDF_pkgdwn.mspx
-----

さて、本題に戻ります。DDKをインストールしたら通常「C:\WinDDK」というフォルダが作成されます。その中に入ると「2600」というフォルダがあり、その中に実質的なWinDDKのファイル群があるわけです。この中のひとつに「src」という、いかにもサンプルプログラムが大量に入ってそうなフォルダが目に付きますが、ご想像の通りサンプルプログラムは初心者がみてもまず理解できないシロモノになっていますので、最初の頃はほぼ無意味です。それでこれらはとりあえず置いといて、ひとつフォルダを上がって「2600」と同じ場所に「hello」というフォルダを作ってください。そしてその中に「hello.cpp」を入れてください。これで、「C:\WinDDK\hello\hello.cpp」というファイルが存在することになります。そして、さらにhelloフォルダ以下に、次の2つのファイルを作成してください。

-----  MAKEFILE
!INCLUDE $(NTMAKEENV)\makefile.def
-----
-----  SOURCES
TARGETNAME=hello
TARGETTYPE=DRIVER
TARGETPATH=obj
SOURCES=hello.cpp
-----

さて、これで準備完了です。「C:\WinDDK\hello\hello.cpp」、「C:\WinDDK\hello\MAKEFILE」、「C:\WinDDK\hello\SOURCES」の3つのファイルを作成したら、いよいよコンパイル(ビルド)となります。Windowsお馴染みのスタートメニューから「スタート→プログラム→Development Kits→Windows DDK 2600→Build Environments→Win XP Checked Build Environment」としてください。もしくはコマンドプロンプトを起動して「C:\WINDDK\2600\bin\setenv.bat C:\WINDDK\2600 chk」と実行してください。すると、以下のような入力画面でコマンドプロンプトが止まります。

-----  コマンドプロンプト
C:\Documents and Settings\kenji>C:\WINDDK\2600\bin\setenv.bat C:\WINDDK\2600 chk
C:\WINDDK\2600>
-----

ここで「cd C:\WINDDK\hello」と入力して場所を移動します。そして、buildと入力することでコンパイルされます。

-----  コマンドプロンプト
C:\WINDDK\2600>cd C:\WINDDK\hello
C:\WINDDK\hello>build
BUILD: Object root set to: ==> objchk
BUILD: Adding /Y to COPYCMD so xcopy ops won't hang.
BUILD: /i switch ignored
BUILD: Compile and Link for i386
BUILD: Loading C:\WINDDK\2600\build.dat...
BUILD: Computing Include file dependencies:
BUILD: Examining c:\winddk\hello directory for files to compile.
    c:\winddk\hello - 1 source files (17 lines)
BUILD: Compiling c:\winddk\hello directory
Compiling - hello.cpp for i386
BUILD: Linking c:\winddk\hello directory
Linking Executable - objchk\i386\hello.sys for i386
BUILD: Done

    2 files compiled
    1 executable built

C:\WINDDK\hello>
-----

無事コンパイルが完了すると、SOURCESファイル、MAKEFILEファイル、hello.cppファイルの他に2つのフォルダと1つのログファイルが作成されます。そしてobjchk\i386フォルダ以下にある「C:\WINDDK\hello\objchk\i386\hello.sys」という拡張子が.sysのファイルが、デバイスドライバファイルです。これでコンパイルは無事完了となります。ちなみにこの例ではチェックビルドを行いましたが、フリービルドでも構いません。64ビット環境の方は、64ビットのチェック(またはフリー)ビルドを行ってください。では、このhello.sysファイルを実行することにします。

実行

実際ビルドして作成されたhello.sysファイルですが、これいったいどうやって実行するのでしょうか? というかそもそも.sysファイルって何だろうと思いますが、それは私にも分かりません。ただドライバは一般的に.sysファイルみたいです。WindowsXPなら「C:\WINDOWS\system32\drivers」以下をみると.sysファイルがたくさんあるのが分かります。

さて、とりあえず実行したいわけですが、ドライバは通常Windows起動時にWindows本体に組み込まれます。ドライバの中にはOSのboot時に実行されるものやシステム初期化時に実行されるものなど、もはやOSの一部となるような動作をするのが当たり前というか、そういう処理が必要なソフトウェアがそもそもドライバを利用するわけです。なので、再起動が日常茶飯事になります。動作を確認するためにはOSの再起動をしなければなりませんし、実行を解除するためにもやはりOSの再起動が必要となります。さらに通常のアプリケーションデバッガは意味を持ちませんので、OllyDbgやVC++についているデバッガも無意味です。なのでデバッグを行う際はSoftICEなどを使うことになるようです。

話がずれてきたので元に戻します。ドライバを実行するためにはレジストリを変更する必要があります。そしてその後再起動を行ないOSに組み込みます。ということで以下のようなファイルを作成します。

-----  hello.reg
REGEDIT4
[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\hello]
"Start"=dword:1
"Type"=dword:1
"ErrorControl"=dword:1
"DisplayName"="hello"
-----

.regファイルはダブルクリックするとレジストリにデータを追加してくれます。ちなみに後で手動で消さなければならないので、「HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\hello」の位置は覚えておいてください。

hello.regをレジストリに追加して、さらにhello.sysファイルを「C:\WINDOWS\system32\drivers」以下にコピーします。これで準備完了です。Windowsを再起動してください。

Windowsが起動するとちゃんとhello.sysが実行されています。といってもそのままでは確認できないため、デバイスドライバを表示するツールを使うことにします。もし私と同じ環境である「WinXP DDK」を使っているならば「Device Tree V2.6」というツールが「WinXP DDK」の中に入っていますのでそれを起動してください。

Device Treeの実行例

もしXP以外のDDKを使っている場合は、「Sysinternals」というサイトから「WinObj」というドライバ閲覧ツールをDLしてください。WinObjを使ってhello.sysドライバを確認したキャプチャー画像が以下です。

WinObjの実行例

Sysinternals:
http://www.microsoft.com/technet/sysinternals/default.mspx

WinObj:
http://www.microsoft.com/technet/sysinternals/utilities/WinObj.mspx

これで無事ドライバがコンパイル実行できました。さて、実際実行されたドライバですが、一度実行してしまうとOS起動中は終了できません。いわばOSに組み込まれている状態ですので、終了させるためには再びレジストリを変更して、hello.sysファイルを削除して、再起動する必要があります。つまりデバイスドライバは、作成後、実行確認と終了時の2回の再起動を必要とするわけです。通常のアプリケーションプログラムのように、.exeファイルをダブルクリックして実行し、×ボタンで終了というように簡単にはいかないのです。なんともメンドクサイですが、これがデバイスドライバの開発みたいです。

では、ファイル名を指定して実行よりregedit.exeを起動して、実行時に登録したレジストリ「HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\hello」を削除してください。さらに、「C:\WINDOWS\system32\drivers」以下にコピーしたhello.sysも削除してください。そして再起動してください。

再起動後、再び「Device Tree V2.6」を使ってドライバを確認してください。helloはもう起動していません。これで終了となります。

サービス制御マネージャー

つまり、ドライバ開発の一連の流れは以下のようになります。

  1. ソースコードを書く
  2. SOURCESファイル、MAKEFILEファイルをそれぞれ作成する
  3. ソースコードのビルドを行う
  4. レジストリを変更し、.sysファイルをdriversファイルにコピーする
  5. 再起動
  6. 実行を確認
  7. レジストリを変更し、.sysファイルをdriversファイルから削除する
  8. 再起動
  9. 終了を確認

そして、実行時にエラーなどがあったらもちろん再度ソースを編集してビルドし、実行を確認しなければならないので、以後3から9を繰り返すことになります。

うーん、かなりメンドクサイです。特に2回の再起動はたえられません。一度実行を確認するだけで、2回も再起動を要するのはさすがにやる気でません。ということで、これを何とか打開しようと筆者(つまり私)は考えました。まぁデバイスドライバですので「一度実行してしまったら、再起動する限り終了できない」というのは分かります。でも実行するくらいは、ボタンひとつでやらせてください。

ここで現れる救世主が「サービス制御マネージャー」です。これを利用すると、なんとボタンひとつでデバイスドライバが起動するツールが作れるらしいのです。ということで実際に作ってみました。

./DIP.zip

ソースコードも添付してますので、興味があったらご覧ください。ソースコードの解説はしませんのでご了承ください。でも毎度のごとくコメントはたくさんつけてます。

起動すると以下のように表示されます。

Driver Install Programの実行例

.sysファイルを選択して「Install」ボタンをクリックするとWindowsにドライバがインストールされます。「Device Tree V2.6」で確認してください。ちなみに、インストールはすぐにできますが、「UnInstall」ボタンを押してもその瞬間からアンインストールはされません。あくまで再起動時(Windows終了時)にアンインストールが実行されます。あと、アンインストールしなければ次回OS起動時もドライバが実行されます。

もし、アンインストールも再起動なしで行いたい場合は、作成するデバイスドライバにUnload処理を加えなければなりません。しかし、まだデバイスドライバのデの字も分からない状態なので、それはまたの機会に解説することにして、とりあえずツールの動作を確認しておいてください。

デバッグ

SoftICEを用意してもらえれば万事解決なのですが、さすがにそうもいかないのでデバッグ環境を整えることにします。というか、デバイスドライバって、CUIプログラムのようにテキストを表示する場所もなければ、Windows(GUI)プログラムのようにウィンドウを描画するわけでもないので、どこまで実行されているのか、どこにエラーがあるのかが、まったくもって分かりません。そもそもデバイスドライバというものそれ自体がWindowsの内部でこっそりと動いているモノであるわけですので、デバッグという観点から見たら、もう救いようがありません。よって、まずはそこから整えていくことにします。

Sysinternals:
http://www.microsoft.com/technet/sysinternals/default.mspx

DebugView:
http://www.microsoft.com/technet/sysinternals/utilities/debugview.mspx

「Sysinternals」というサイトに「DebugView」なるツールがあります。まずはこれをDLしてください。現在のバージョンは4.3です。これはドライバ開発者の中ではかなり有名なツールらしく、結構いろいろな人に使われているようです。さて、このツール、その名の通りデバッグ情報を出力してくれるだけなのですが、OutputDebugStringからのデータだけでなく、DbgPrintからのデータも表示してくれるところがGOODです。つまりカーネルモードからのデバッグ情報も表示してくれるわけです。ちなみにDbgPrintはカーネルモードのOutputDebugStringみたいなもので、MSDNでは以下のように定義されています。

DbgPrint - MSDN:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ddtools/hh/ddtools/DebugFns_5e11bbcc-adc2-46c0-b371-0e54c50bb2dc.xml.asp

-----
ULONG
    DbgPrint(IN PCHAR  Format,
             ...[arguments]
    );
-----

みて分かるとおり、使い方はprintfと同じです。では、これを利用してhello.cppを改良します。以下のプログラムをみてください。

-----  Dhello.cpp
#ifdef __cplusplus
extern "C" {
#endif

#include <wdm.h>

NTSTATUS DriverEntry(PDRIVER_OBJECT, PUNICODE_STRING);

#ifdef __cplusplus
}
#endif

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
                     IN PUNICODE_STRING pRegistryPath)
{
    DbgPrint("Hello World! from kernel mode.");
    return STATUS_SUCCESS;
}
-----
-----  MAKEFILE
!INCLUDE $(NTMAKEENV)\makefile.def
-----
-----  SOURCES
TARGETNAME=Dhello
TARGETTYPE=DRIVER
TARGETPATH=obj
SOURCES=Dhello.cpp
-----

簡単ですね。DbgPrintが追加されただけです。ではこれをビルドしてください。hello.sysの時は再起動しましたが、今回は「サービス制御マネージャー」の項で作成したツールを使ってください。

まずは、Dbgview.exeを起動します。これはいろいろと設定できるようですが、私の場合はDLした状態(デフォルト)のままでOKでしたので、何も変更しない状態で起動してください。起動すると以下のようなウィンドウが表示されます。

Dbgviewの実行例

そして、起動した状態のまま、今度は「Driver Install Program」を使って、Dhello.sysをインストールしてください。無事インストールが完了したら、Dbgview.exeのウィンドウを開いてください。以下のように表示されます。

Dbgviewの実行例2

もし表示されない場合は「DebugPort」というキーワードで検索してください。どうやらデバイスドライバというやつは、大抵PCを2台用意してデバッグを行うみたいで、2台のPCをネットワークで繋いだ状態で片方がデバッグマシン、もう片方が実行マシンとしてデバッグ作業を行うみたいです。なので、外部のPCに出力する設定になってたりする場合はうまくいかないかもしれません。もうその辺りはよく分かりません。ごめんなさい。

さて、これでとりあえずDbgPrintを使ってデバイスドライバからの出力が受けられるようになりました。もっと大きなプログラムを作成するのならSoftICEのような有用なデバッガを用いて、本格的に処理を追っていかなければなりませんが、個人で作るような小さなプログラムならば、DbgPrintとDbgview.exeで十分でしょう。今後は「DbgPrint」と「Dbgview.exe」そして「サービス制御マネージャー」を利用して作成した「Driver Install Program」を使って、デバイスドライバ作成講座を進めていくことにします。

今回のサンプルプログラムである「hello」と「Dhello」は、ZIP圧縮して以下のアドレスにアップしました。参考にしてください。

./driver1.zip

さいごに

さて、いかがだったでしょうか。今回は「Windows Device Driver Programming Part 1」と題してお送りしましたが、「Part 2」があるかどうかは定かではありません(「デバイスドライバ作成講座を進めていくことにします」と言っているにも関わらず(笑))。というのも、こんなマニアックなことを書いてると「本当に誰も読んでくれないのでは?」という不安がよぎるからです。さすがにモチベーションが保てません(^^;。なので、こういう読者層が極度に狭いネタは自分のHPでこっそり公開しようかなぁと考えていますが、そんな時間もないですし「そもそもお前はたくさんの人に読んでもらえるようなモノを書けるのか?」と言われると、書けないです(^^;。なのでこのまま突き進んでいくことにします(笑)。ただ、せっかくデバドラ関連の記事を書いたので、今後はMACフレームレベルでのパケット取得方法や、カーネルモードプログラムのAPIフック方法などを解説していけたらなと思います。さて、最後になりましたが、ここまで読んでくれて本当にありがとうございます。

では、また会う日まで...

参考サイト

  1. Driver Development Part 1: Introduction to Drivers
  2. Driver Development Part 2: Introduction to Implementing IOCTLs
  3. Driver Development Part 3: Introduction to driver contexts
  4. Driver Development Part 4: Introduction to device stacks
  5. Driver Development Part 5: Introduction to the Transport Device Interface

Copyright (C) 2003-2005 Kenji Aiko All Rights Reserved