MapPtrToProcess 用法 WINCE驅(qū)動分析3
以使用下面的應用程序代碼測試這個driver,使用evc編譯。
#include windows.h>
#includeWindev.h>
#include stdio.h>
#include "objbase.h"
#include "initguid.h"
#include "foo.h"
//char data1[10];
int WinMain(void)
{
HANDLE hnd;
COPY_STRUCT cs[1];
int i;
//static char data1[10];
auto char data1[10];
auto char data2[10];
static char* p1,*p2;
//cs.pBuffer1 = (char *)malloc(10);
//cs.pBuffer2 = (char*)malloc(10);
//cs.nLen = 10;
p1 = (char *)LocalAlloc(LPTR,10);
p2 = (char *)malloc(10);
//cs[0].pBuffer1 = (char *)malloc(10);
//cs[0].pBuffer2 = (char*)malloc(10);
cs[0].pBuffer1 = data1[0];
cs[0].pBuffer2 = data2[0];
cs[0].nLen = 10;
memset(cs[0].pBuffer1,'a',10);
hnd = CreateFile(FOO_DEV_NAME,GENERIC_READ|GENERIC_WRITE,0,NULL,0,0,NULL);
if(hnd==NULL)
{
printf("Open device falied!n");
return;
}
DeviceIoControl(hnd,IOCTL_FOO_XER,cs[0],sizeof(COPY_STRUCT),NULL,0,NULL,NULL);
//for(i=0;i9;i++)
//{
//printf(" %c",*(cs.pBuffer2++));
//}
printf("n");
CloseHandle(hnd);
// free(cs[0].pBuffer1);
// free(cs[0].pBuffer2);
}
可以通過evc的單步調(diào)試看結(jié)果。好了一切都完成了,我們來看看系統(tǒng)是怎么工作的吧,從應用程序開始,
CreateFile(FOO_DEV_NAME,GENERIC_READ|GENERIC_WRITE,0,NULL,0,0,NULL);
會調(diào)用到
FOO_Open(DWORD dwContext, DWORD AccessCode, DWORD ShareMode)
而FOO_DEV_NAME名字定義在foo.h里面。
#define FOO_DEV_NAME L"Foo1:"
注意后面是 1 ,這個是和注冊表的這一項匹配的
"Index"=dword:1
當調(diào)用CreateFile發(fā)生了什么,slot之間的轉(zhuǎn)換,一系列系統(tǒng)操作后,調(diào)用到我們自己的driver函數(shù)FOO_Open,在這個函數(shù)里我們返回了一個句柄,它可以用來存儲我們的自己driver的信息。在其它I/O操作中可以使用。
Driver什么時候加載的?在注冊表里,device manager會一個個的加載,會調(diào)用到FOO_Init函數(shù)。這個函數(shù)返回一個指針,在調(diào)用FOO_Open又傳回來了,這樣我們就可以實現(xiàn)初始化一些自己driver的東西。
接著一個重要的函數(shù),
DeviceIoControl(hnd,IOCTL_FOO_XER,cs[0],sizeof(COPY_STRUCT),NULL,0,NULL,NULL);
調(diào)用到
FOO_IOControl
走到這里
case IOCTL_FOO_XER:
if((pInBuf==NULL))
{
SetLastError(ERROR_INVALID_PARAMETER);
break;
}
pcs = (COPY_STRUCT*)pInBuf;
__try{
pMap1 = MapPtrToProcess(pcs->pBuffer1,GetCallerProcess());
pMap2 = MapPtrToProcess(pcs->pBuffer2,GetCallerProcess());
DEBUG_OUT(1, (TEXT("+FOO_IOControl(0x%x,0x%x)rn"),pcs->pBuffer1,pcs->pBuffer2));
memcpy(pcs->pBuffer2,pcs->pBuffer1,pcs->nLen);
bResult = TRUE;
}
__except(EXCEPTION_EXECUTE_HANDLER){
DEBUG_OUT(1,(TEXT("Exception:FOO_IOCTLrn")));
break;
}
break;
default:
break;
這里又很多東西要研究,
從應用程序傳來的參數(shù)有, control code,IOCTL_FOO_XER和一個重要的輸入?yún)?shù)cs[0],它是一個指針。cs 是一個結(jié)構(gòu)體,定義在FOO.H
typedef struct {
char* pBuffer1;
char* pBuffer2;
int nLen;
}COPY_STRUCT;
而且這個結(jié)構(gòu)體里有兩個指針。
DeviceIoControl 傳過來的指針可以用嗎?它包含的兩個指針可以直接用嗎?
按照PB連接幫助文檔看,
The operating system (OS ) manages pointers passed directly as parameters. Drivers must map all pointers contained in structures. DeviceIoControl buffers are often structures that contain data, some of which might be pointers.
You can map a pointer contained in a structure by calling MapPtrToProcess, setting the first parameter to the pointer, and then setting the second parameter to GetCallerProcess.
cs指針已經(jīng)映射好了,但是它指向的結(jié)構(gòu)里的指針我們需要自己使用MapPtrToProcess函數(shù)映射。
這也就是:
pMap1 = MapPtrToProcess(pcs->pBuffer1,GetCallerProcess());
pMap2 = MapPtrToProcess(pcs->pBuffer2,GetCallerProcess());
的由來,可是后面的代碼沒有使用pMap1,pMap2。而是直接使用:
memcpy(pcs->pBuffer2,pcs->pBuffer1,pcs->nLen);
而且它還工作了,沒有出現(xiàn)exception。很奇怪。我第一次在一個家伙的代碼里看見這種情況,很吃驚,但是它工作的很好,是文檔出錯了?
我們來分析一下,看看應用程序的代碼:
COPY_STRUCT cs[1];
auto char data1[10];
auto char data2[10];
cs結(jié)構(gòu)和data1,data2數(shù)組都是自動變量,存放在堆棧里。假設這個應用程序被加載到0x18000000位置的slot里,那么他們的地址都是0x18XXXXXX。不熟悉wince memory architecture的可以看看資料,了解一下slot。當調(diào)用了
DeviceIoControl,按照文檔的說法,cs指針得到了轉(zhuǎn)換,因為從應用程序的進程轉(zhuǎn)到了device.exe進程,而device進程又是當前的運行的進程,被映射到了slot0,系統(tǒng)負責轉(zhuǎn)換cs指針。而cs包含的pBuffer1和pBuffer2是沒有映射不能直接用的。
事實上,我們傳過來的指針根本就是不需要映射,因為他們都是0x18xxxxxx,在應用程序的slot里,所以只要device.exe有訪問應用程序的權(quán)限,就可以了。而這個權(quán)限,系統(tǒng)已經(jīng)幫我們設置好了。
那什么情況下要自己映射呢?
如果應用程序在定義 data1和data2使用static關鍵字,或者使用LocalAlloc,HeapAlloc的時候,一定要自己映射cs里的指針。
在應用程序里這樣寫:
cs.pBuffer1 = (char *)malloc(10);
cs.pBuffer2 = (char*)malloc(10);
cs.nLen = 10;
如果不使用MapPtrToProcess完成映射,那就出現(xiàn)data abort exception.
為什么呢?
因為這些變量都是在堆里分配的,而當應用程序運行時,被映射到slot0,堆的地址也就是處于slot的范圍內(nèi),傳遞到device.exe后,device.exe被映射到了slot0,這個時候必須要將應用程序的指針映射回應用程序所在的slot。否則訪問的是device.exe的空間,會發(fā)生不可知道的結(jié)果。
驗證一下上面說的地址分配問題。
我們這樣定義
COPY_STRUCT cs[1];
static char data1[10]; 堆里
auto char data2[10]; 棧里
這樣賦值:
cs[0].pBuffer1 = data1[0];
cs[0].pBuffer2 = data2[0];
cs[0].nLen = 10;
調(diào)試信息:
cs[0].pBuffer1 = data1[0];
180112D0 ldr r2, [pc, #0xD0]
180112D4 str r2, [sp, #0x10]
讀取data1[0]使用的是PC作為基址,而此時的應用程序處于運行階段映射到slot0,那么pc也就在0~01ffffff范圍,我的調(diào)試結(jié)果是在0x000112D0+8,使用的是arm,流水線機制,當前指令地址+8才是pc值。
143: cs[0].pBuffer2 = data2[0];
180112D8 add r0, sp, #0x20
180112DC str r0, [sp, #0x14]
讀取data2[0]采用的是sp作為基址,sp在應用程序加載到slot的時候就確定了的。所以保持了在應用程序slot的值,處于0x18xxxxxx范圍。
我們看到因為wince的slot機制,我們有時候需要映射,有時候不需要。所以wince文檔說結(jié)構(gòu)里的指針要映射。畢竟你不知道應用程序怎么寫。
當然,你可以根本不映射,只要把那個結(jié)構(gòu)屏蔽調(diào),寫一個STATIC LIBRARY給用戶使用,自己保證使用正確的地址分配就可以了。上面我說的那個家伙就是這么干的。
好了,接著調(diào)用:
CloseHandle(hnd);
程序結(jié)束了,完成了一次簡單的拷貝。
這個框架完成了,driver的基本接口設計,強調(diào)了內(nèi)存指針的使用問題。但是相對于一個真正的driver,還缺少點東西,就是訪問硬件的方法。下面繼續(xù)討論如何訪問硬件
linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)
評論