MapPtrToProcess 用法 WINCE驅(qū)動(dòng)分析3
以使用下面的應(yīng)用程序代碼測(cè)試這個(gè)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)是怎么工作的吧,從應(yīng)用程序開始,
CreateFile(FOO_DEV_NAME,GENERIC_READ|GENERIC_WRITE,0,NULL,0,0,NULL);
會(huì)調(diào)用到
FOO_Open(DWORD dwContext, DWORD AccessCode, DWORD ShareMode)
而FOO_DEV_NAME名字定義在foo.h里面。
#define FOO_DEV_NAME L"Foo1:"
注意后面是 1 ,這個(gè)是和注冊(cè)表的這一項(xiàng)匹配的
"Index"=dword:1
當(dāng)調(diào)用CreateFile發(fā)生了什么,slot之間的轉(zhuǎn)換,一系列系統(tǒng)操作后,調(diào)用到我們自己的driver函數(shù)FOO_Open,在這個(gè)函數(shù)里我們返回了一個(gè)句柄,它可以用來存儲(chǔ)我們的自己driver的信息。在其它I/O操作中可以使用。
Driver什么時(shí)候加載的?在注冊(cè)表里,device manager會(huì)一個(gè)個(gè)的加載,會(huì)調(diào)用到FOO_Init函數(shù)。這個(gè)函數(shù)返回一個(gè)指針,在調(diào)用FOO_Open又傳回來了,這樣我們就可以實(shí)現(xiàn)初始化一些自己driver的東西。
接著一個(gè)重要的函數(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;
這里又很多東西要研究,
從應(yīng)用程序傳來的參數(shù)有, control code,IOCTL_FOO_XER和一個(gè)重要的輸入?yún)?shù)cs[0],它是一個(gè)指針。cs 是一個(gè)結(jié)構(gòu)體,定義在FOO.H
typedef struct {
char* pBuffer1;
char* pBuffer2;
int nLen;
}COPY_STRUCT;
而且這個(gè)結(jié)構(gòu)體里有兩個(gè)指針。
DeviceIoControl 傳過來的指針可以用嗎?它包含的兩個(gè)指針可以直接用嗎?
按照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。很奇怪。我第一次在一個(gè)家伙的代碼里看見這種情況,很吃驚,但是它工作的很好,是文檔出錯(cuò)了?
我們來分析一下,看看應(yīng)用程序的代碼:
COPY_STRUCT cs[1];
auto char data1[10];
auto char data2[10];
cs結(jié)構(gòu)和data1,data2數(shù)組都是自動(dòng)變量,存放在堆棧里。假設(shè)這個(gè)應(yīng)用程序被加載到0x18000000位置的slot里,那么他們的地址都是0x18XXXXXX。不熟悉wince memory architecture的可以看看資料,了解一下slot。當(dāng)調(diào)用了
DeviceIoControl,按照文檔的說法,cs指針得到了轉(zhuǎn)換,因?yàn)閺膽?yīng)用程序的進(jìn)程轉(zhuǎn)到了device.exe進(jìn)程,而device進(jìn)程又是當(dāng)前的運(yùn)行的進(jìn)程,被映射到了slot0,系統(tǒng)負(fù)責(zé)轉(zhuǎn)換cs指針。而cs包含的pBuffer1和pBuffer2是沒有映射不能直接用的。
事實(shí)上,我們傳過來的指針根本就是不需要映射,因?yàn)樗麄兌际?x18xxxxxx,在應(yīng)用程序的slot里,所以只要device.exe有訪問應(yīng)用程序的權(quán)限,就可以了。而這個(gè)權(quán)限,系統(tǒng)已經(jīng)幫我們?cè)O(shè)置好了。
那什么情況下要自己映射呢?
如果應(yīng)用程序在定義 data1和data2使用static關(guān)鍵字,或者使用LocalAlloc,HeapAlloc的時(shí)候,一定要自己映射cs里的指針。
在應(yīng)用程序里這樣寫:
cs.pBuffer1 = (char *)malloc(10);
cs.pBuffer2 = (char*)malloc(10);
cs.nLen = 10;
如果不使用MapPtrToProcess完成映射,那就出現(xiàn)data abort exception.
為什么呢?
因?yàn)檫@些變量都是在堆里分配的,而當(dāng)應(yīng)用程序運(yùn)行時(shí),被映射到slot0,堆的地址也就是處于slot的范圍內(nèi),傳遞到device.exe后,device.exe被映射到了slot0,這個(gè)時(shí)候必須要將應(yīng)用程序的指針映射回應(yīng)用程序所在的slot。否則訪問的是device.exe的空間,會(huì)發(fā)生不可知道的結(jié)果。
驗(yàn)證一下上面說的地址分配問題。
我們這樣定義
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作為基址,而此時(shí)的應(yīng)用程序處于運(yùn)行階段映射到slot0,那么pc也就在0~01ffffff范圍,我的調(diào)試結(jié)果是在0x000112D0+8,使用的是arm,流水線機(jī)制,當(dāng)前指令地址+8才是pc值。
143: cs[0].pBuffer2 = data2[0];
180112D8 add r0, sp, #0x20
180112DC str r0, [sp, #0x14]
讀取data2[0]采用的是sp作為基址,sp在應(yīng)用程序加載到slot的時(shí)候就確定了的。所以保持了在應(yīng)用程序slot的值,處于0x18xxxxxx范圍。
我們看到因?yàn)閣ince的slot機(jī)制,我們有時(shí)候需要映射,有時(shí)候不需要。所以wince文檔說結(jié)構(gòu)里的指針要映射。畢竟你不知道應(yīng)用程序怎么寫。
當(dāng)然,你可以根本不映射,只要把那個(gè)結(jié)構(gòu)屏蔽調(diào),寫一個(gè)STATIC LIBRARY給用戶使用,自己保證使用正確的地址分配就可以了。上面我說的那個(gè)家伙就是這么干的。
好了,接著調(diào)用:
CloseHandle(hnd);
程序結(jié)束了,完成了一次簡(jiǎn)單的拷貝。
這個(gè)框架完成了,driver的基本接口設(shè)計(jì),強(qiáng)調(diào)了內(nèi)存指針的使用問題。但是相對(duì)于一個(gè)真正的driver,還缺少點(diǎn)東西,就是訪問硬件的方法。下面繼續(xù)討論如何訪問硬件
linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)
評(píng)論