WinCE6.0のメモリ空間について理解が進んだところで、カーネルモードドライバ(KMドライバ)からどのようにユーザーモードのプロセス空間をアクセスするのかという事を確認します。確認のためにサンプルアプリケーションとサンプルドライバをそれぞれダウンロードしてください。このサンプルソースはとあるセミナーで使用したもので、探せば他のサイトからもダウンロードできます。
なお念のためですが、このサンプルソースの再頒布、改変等は商用・非商用にかかわらず自由です。ただし著作権は私に帰属します。もっともソース自体は何も無くてお恥ずかしい限りです。
ダウンロードしたファイルを展開するとプロジェクトファイルが展開されます。適当なOSイメージを作って、そこにこれらのサブプロジェクトを追加します。OSイメージの構築はこちらを参考にしてください。サブプロジェクトの追加は、PBのメニューから“File:Add:Existing Subproject...”を選択し、追加するプロジェクトファイルを取り込むことでできます。取り込んだサブプロジェクトは、いつものように“Buil:Advanced Build Command:Build Current BSP and Subproject”メニューでnk.binに組み込みます。
さてここで今回確認をするソースコードのポイントを説明します。まずはドライバです。このドライバはビルトインのKMドライバとして動作しますので、OSイメージを立ち上げるとそのままnk.exe(カーネルプロセス)にロードされます。実際にOSイメージを立ち上げた状態で、Process Viewerで確認すると、以下のようにDriver01.dllが0xc1070000、すなわちカーネル空間にロードされていることがわかります。
このDriver01.dllのDRV_IOControl関数では、CMD_READ_1BYTE、CMD_WRITE_1BYTEというコマンドが準備されています。CMD_READ_1BYTEではカーネルプロセスヒープから1Byte読み込んで、ユーザープロセスから渡されたポインタにデータを書き込みます。CMD_WRITE_1BYTEではユーザープロセスから渡されたポインタのデータ1Byteをカーネルプロセスヒープに書き込みます。
【DRV_Init関数:カーネルプロセスヒープの取得】
pHWHead->pWorkBase = VirtualAlloc(
NULL,
DRIVER_WORK_SIZE,
MEM_COMMIT,
PAGE_READWRITE
);
【DRV_IOControl関数:データのRead/Write】
case CMD_READ_1BYTE:
{
PDRV01_RWBUFFER pBuffer = (PDRV01_RWBUFFER)pInBuf;
if ( pBuffer->dwOffset < DRIVER_WORK_SIZE ){ // Data offset check
*pBuffer->pData = *((LPBYTE)pHWHead->pWorkBase + pBuffer->dwOffset);
}
else{
RetVal = FALSE;
}
}
break;
case CMD_WRITE_1BYTE:
{
PDRV01_RWBUFFER pBuffer = (PDRV01_RWBUFFER)pInBuf;
if ( pBuffer->dwOffset < DRIVER_WORK_SIZE ){ // Data offset check
*((LPBYTE)pHWHead->pWorkBase + pBuffer->dwOffset) = *pBuffer->pData;
}
else{
RetVal = FALSE;
}
}
break;
次にアプリケーションの呼び出しの部分を見てみます。ここではドライバに渡すための引数として、Drv01Buffer変数を定義し、そのメンバ変数としてのポインタDrv01Buffer.pDataに&nBufferをセットしています。したがってドライバDriver01.dllにはこの&nBuffer、すなわちユーザープロセス空間に確保されたスタックポインタが渡されることになります。
【WinMain関数:ドライバの呼び出し】
DRV01_RWBUFFER Drv01Buffer;
BYTE nBuffer;
...
// Driver Driver01 open
hDrv01 = CreateFile (
TEXT("DRV1:"),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if ( !hDrv01 ) { // don't open
RETAILMSG (1, (TEXT("Driver01 open function is failed.\r\n")));
return 0;
}
// I will set data for Driver01 work area.
Drv01Buffer.pData = &nBuffer;
for ( i = 0; i < 32 ; i++ ){
Drv01Buffer.dwOffset = i;
*Drv01Buffer.pData = (BYTE)(i & 0xff);
DeviceIoControl(
hDrv01,
CMD_WRITE_1BYTE,
(LPVOID)&Drv01Buffer,
sizeof(DRV01_RWBUFFER),
NULL,
0,
&dwReturnValue,
NULL
);
}
...
実際にこのアプリケーションを実行してみましょう。“Target:Run Programs...”メニューからDrv01TestApp.exeを選択して実行します。その際にDrv01TestApp.cのDeviceIOControl呼び出しと、Driver01.cのCMD_WRITE_1BYTEでブレークポイントを設定する事を忘れないでください。
【Drv01TestApp.c:WinMain関数】
DeviceIoControl(
hDrv01,
CMD_WRITE_1BYTE,
(LPVOID)&Drv01Buffer,
sizeof(DRV01_RWBUFFER),
NULL,
0,
&dwReturnValue,
NULL
); ← ブレークポイントを設定
【Driver01.c:DRV_IOControl関数】
case CMD_WRITE_1BYTE:
{
PDRV01_RWBUFFER pBuffer = (PDRV01_RWBUFFER)pInBuf; ← ブレークポイントを設定
実行すると最初にDeviceIOControl呼び出しでブレークがかかります。ここで引数として渡すローカル変数Drv01Bufferの値を確認します。Drv01Buffer.pDataの値はこの例では0x1801fcd4ということで、このユーザープロセスの空間を指していることがわかります。このアドレスはDrv01TestApp.exe自身のプロセス空間ですから、当然そのままでアクセスができます。
引数を確認したらF5を押して再実行してください。次にDriver01.cのCMD_WRITE_1BYTEでブレークします。そしてF10キーを押してもう1Step実行します。。この時点でローカル変数pBufferに引数のポインタがセットされます。ここでpBuffer->pDataを確認するとユーザー空間のアドレスが入っていることがわかります。この値はテストアプリケーションDrv01TestApp.exeが渡した値そのものの0x1801fcd4がセットされています。
ここで疑問が生じます。それはユーザーモードのプロセスは、最大で32K個のプロセスが同じ仮想アドレス空間を持っているということです。ということであれば、このKMドライバがこの“0x1801fcd4”をアクセスしようとしてもどのプロセス空間かわからないのではないか?という疑問が沸いてくるのです。確かに適当なプロセス空間をアクセスしても、呼び出したプロセスのポインタで無いとアクセス例外が出るか、たまたまアロケートされていたアドレスのデータが読み出されるはずです。
そこでCall Stack(呼び出し履歴)を見てみることにします。するとこの呼び出されたKMドライバは呼び出したプロセス(Drv01TestApp.exe)からそのまま呼び出されていることがわかります。すなわち呼び出されたKMドライバは、呼び出したユーザー空間のプロセスを特定することができているのです。
イメージとして捕らえると下図のようになります。KMドライバは呼び出したプロセスと同一のコンテキストで、リニアにつながった仮想アドレスとして動作しているようです。
ただこのあたりはもう少し検証が必要で、同一のドライバを異なるプロセスが同時に呼び出した場合、そのユーザー空間のアドレスはどうなるのかという疑問が残っています。
【追記】
気になったので異なるプロセスから同一のKMドライバを呼び出した場合、そのユーザープロセスの空間がどう見えるかを試してみました。すると呼び出されたDriver01.dllからはそれぞれ呼び出したプロセス空間をそのままアクセスすることができます。そして呼び出したプロセス以外の空間はアクセスすることができません。したがってKMドライバは呼び出したプロセスのコンテキストとして動作しているようで、ユーザー空間の保護が働いています。
ただしカーネル空間のヒープとしてアロケートされるKMドライバのワークエリアは、ユーザー空間とは関係無く、カーネルモードとして管理されます。すなわちそのワークエリアやマッピングされたレジスタの排他制御は、KMドライバ自身でしっかりと行わないとデータの破壊が起きるので注意が必要です。これはWinCE5.0以前も、WinCE6.0も同じです。
続く...
Recent Comments