前言
在64-bit作業系統上所使用的驅動程式,一定是64-bit,然而應用程式卻可能是32或64-bit。當32-bit應用程式傳值給驅動程式時是有可能會發生溢位的。主要原因是32與64-bit指標所佔用記憶體長度的不同。接下來我將透過Reference 1中的程式PhyMem,來告訴大家問題在哪與如何修改。(最後改完的程式碼恕我不提供)
PhyMem介紹
這是一個中國人寫的程式。作用與WinIO相同,可以存取windows的io port與physical memory。這個程式包含pmdll、driver與test三個專案,分別產生dll、sys與exe。作者僅提供32-bit的版本,但只要修改編譯設定就可以讓它產生出64-bit的artifact。但如同我前言所說,如果你是32-bit的dll要存取64的sys該怎麼辦? 可以規定User在64-bit的OS用64-bit的應用程式就好了阿! 但是將一個32-bit應用程式改為64-bit的有這麼簡單嗎? 除此之外,中間傳遞的資料型態也會影響到正常功能。
設定driver專案
我開發環境是VC2008與DDK6000,下載Reference1的專案是無法直接編譯的,可以參考我的設定去修改編譯、連結參數。輸出檔可以看個人需求,根據32或64命名,或者是用同一個檔案名稱。
32-bit
- C/C++ > 一般 > 其它Include目錄($(DDKROOT)為設定於環境變數的DDK安裝目錄):
$(DDKROOT)\inc\ddk";"$(DDKROOT)\inc\api";"$(DDKROOT)\inc\crt";"$(DDKROOT)\inc\crt\gl";"$(DDKROOT)\inc\crt\sys"
- 連結器 > 一般 > 其它程式庫目錄: $(DDKROOT)\lib\wnet\i386。
- 連結器 > 資訊清單檔: 將產生資訊清單與UAC選擇否,因為這不適用於driver。
- 連結器 > 進階 > 隨機化的基底位置: 選擇預設。
64-bit
Reference1僅提供32-bit設定。64-bit可在建置>組態管理員中,新增x64平台,而設定檔可從win32複製過來修改。修改內容如下:
- C/C++ > 前置處理器: 將_X86_替換為_AMD64_,這會影響到使用的資料結構。
- 連結器 > 一般 > 其它程式庫目錄: $(DDKROOT)\lib\wnet\amd64。
問題在哪?
首先看pmdll這個專案中讀physical memory的程式碼,它做的事情就是將address與size,透過driver mapping出來。driver會assign到pVirAddr上,最後回傳給callee。
PVOID MapPhyMem(DWORD phyAddr, DWORD memSize) { PVOID pVirAddr=NULL; //mapped virtual addr PHYMEM_MEM pm; DWORD dwBytes=0; BOOL bRet=FALSE; pm.pvAddr=(PVOID)phyAddr; //physical address pm.dwSize=memSize; //memory size if (hDriver!=INVALID_HANDLE_VALUE) { bRet=DeviceIoControl(hDriver, IOCTL_PHYMEM_MAP, &pm, sizeof(PHYMEM_MEM), &pVirAddr, sizeof(PVOID), &dwBytes, NULL); } if (bRet && dwBytes==sizeof(PVOID)) return pVirAddr; else return NULL; }
發現問題了嗎? 還沒有沒關係,接著來看看driver主要的程式碼。它會把dll的input assign到local variable,並透過sizeof去確認PHYMEM_MEM與PVOID是否相同,決定要不要做我看不懂的事mapping memory。
//Get the pointer to the input/output buffer and it's length pSysBuf=(PVOID)irp->AssociatedIrp.SystemBuffer; pMem=(PPHYMEM_MEM)pSysBuf; pPort=(PPHYMEM_PORT)pSysBuf; pPci=(PPHYMEM_PCI)pSysBuf; dwInBufLen=irpStack->Parameters.DeviceIoControl.InputBufferLength; dwOutBufLen=irpStack->Parameters.DeviceIoControl.OutputBufferLength; switch (irpStack->MajorFunction) { case IRP_MJ_DEVICE_CONTROL: dwIoCtlCode=irpStack->Parameters.DeviceIoControl.IoControlCode; switch (dwIoCtlCode) { case IOCTL_PHYMEM_MAP: if (dwInBufLen==sizeof(PHYMEM_MEM) && dwOutBufLen==sizeof(PVOID)) { PHYSICAL_ADDRESS phyAddr; PVOID pvk, pvu; ...以下省略
主要的問題是: PPHYMEM_MEM這個結構的大小,在32-bit dll與64-bit sys中是一樣的嗎? 如果不一樣,勢必造成轉型與sizeof check問題。我們可以透過下面的程式碼去確認:
typedef struct tagPHYMEM_MEM { PVOID pvAddr; //physical addr when mapping, virtual addr when unmapping ULONG dwSize; //memory size to map or unmap } PHYMEM_MEM, *PPHYMEM_MEM; int _tmain(int argc, _TCHAR* argv[]) { cout << "PVOID: " << sizeof(PVOID) << endl; cout << "ULONG: " << sizeof(ULONG) << endl; cout << "PHYMEM_MEM: " << sizeof(PHYMEM_MEM) << endl; return 0; }
32-bit執行檔 vs 64-bit執行檔執行結果:
從執行結果可以發現32與64-bit在data type與structure size的不同。參考Reference 2~4介紹,32-bit and 64-bit data models至少有四種model,Windows使用的是LLP64,只有pointer會從32被擴展到64-bit。而structure的部分,在32-bit如果是1個byte,會被擴展而4個byte,64位元則會被擴展為8個byte,目的是欄位的對齊。因此把32-bit的dll資料結構assign給64-bit的driver,是對不起來的! 這就像是審預算,基層只A一點錢,越上層需要越多的filler,最後無法對齊造成分贓不均,才導致海角7億ㄅㄧㄚㄎㄤ。
修改
針對這個問題,我的想法就是: A多少錢就要平分給基層人員。用在32-bit與64-bit會一樣大小的data type不就好了嗎? 我第一個修改的是資料結構。
data structure
PVOID在32-bit是4 bytes而64-bit是8 bytes,我們可以改用PVOID64確保在32與64-bit都是 8bytes。
typedef struct tagPHYMEM_MEM { PVOID pvAddr; //physical addr when mapping, virtual addr when unmapping ULONG dwSize; //memory size to map or unmap } PHYMEM_MEM, *PPHYMEM_MEM;
to
typedef struct tagPHYMEM_MEM { PVOID64 pvAddr; //physical addr when mapping, virtual addr when unmapping ULONGLONG dwSize; //memory size to map or unmap } PHYMEM_MEM, *PPHYMEM_MEM;
你也許會問: 不是有問題的只有PVOID的sizeof,為什麼我連dwSize都一起改了呢? 一個原因是希望讓在64-bit也是使用8 bytes的型態。也許做assign時,會因為剛好它在最後一個field而不會有問題,但如果它是一段連續的記憶體位置呢? 另外一個原因是32-bit與64-bit的定址不同。64-bit的記憶體位置比32-bit大了一倍,用ULONG無法描述0x0000000000000000~0xFFFFFFFFFFFFFFFF。
dll
dll的部分就是將原本PVOID的部分改為使用PVOID64。function的參數僅使用DWORD,如果要支援mapping更大的記憶體位置,可以改用DWORD64,但值傳到driver層要小心溢位問題。
PVOID MapPhyMem(DWORD phyAddr, DWORD memSize) { PVOID pVirAddr=NULL; //mapped virtual addr PHYMEM_MEM pm; DWORD dwBytes=0; BOOL bRet=FALSE; pm.pvAddr=(PVOID)phyAddr; //physical address pm.dwSize=memSize; //memory size if (hDriver!=INVALID_HANDLE_VALUE) { bRet=DeviceIoControl(hDriver, IOCTL_PHYMEM_MAP, &pm, sizeof(PHYMEM_MEM), &pVirAddr, sizeof(PVOID), &dwBytes, NULL); } if (bRet && dwBytes==sizeof(PVOID)) return pVirAddr; else return NULL; }
to
PVOID MapPhyMem(DWORD phyAddr, DWORD memSize) { PVOID64 pVirAddr=NULL; //mapped virtual addr PHYMEM_MEM pm; DWORD dwBytes=0; BOOL bRet=FALSE; pm.pvAddr=(PVOID64)phyAddr; //physical address pm.dwSize=memSize; //memory size if (hDriver!=INVALID_HANDLE_VALUE) { bRet=DeviceIoControl(hDriver, IOCTL_PHYMEM_MAP, &pm, sizeof(PHYMEM_MEM) , &pVirAddr, sizeof(PVOID64), &dwBytes, NULL); } if (bRet && dwBytes==sizeof(PVOID64)) return (PVOID)pVirAddr; else return NULL; }
driver
修改的內容,除了sizeof(PVOID)的判斷改為dwOutBufLen==sizeof(PVOID64),還有宣告的型態。這裡會有個問題是: phyAddr.QuadPart所使用的是LONGLONG,當要存取0x8000000000000000以上的記憶體時,我想應該會出問題。目前我不曉得該如何解決,而且還沒有人使用到這麼大的記憶體吧? Windows所允許的記憶體大小也不到這。
switch (irpStack->MajorFunction) { case IRP_MJ_DEVICE_CONTROL: dwIoCtlCode=irpStack->Parameters.DeviceIoControl.IoControlCode; switch (dwIoCtlCode) { case IOCTL_PHYMEM_MAP: if (dwInBufLen==sizeof(PHYMEM_MEM) && dwOutBufLen==sizeof(PVOID)) { PHYSICAL_ADDRESS phyAddr; PVOID pvk, pvu; phyAddr.QuadPart=(ULONGLONG)pMem->pvAddr; ...以下省略
to
switch (irpStack->MajorFunction) { case IRP_MJ_DEVICE_CONTROL: dwIoCtlCode=irpStack->Parameters.DeviceIoControl.IoControlCode; switch (dwIoCtlCode) { case IOCTL_PHYMEM_MAP: DebugPrint("dwInBufLen=%d,sizeof(PHYMEM_MEM)=%d", dwInBufLen, sizeof(PHYMEM_MEM)); DebugPrint("dwOutBufLen=%d,sizeof(PVOID)=%d", dwOutBufLen, sizeof(PVOID64)); if ( dwInBufLen==sizeof(PHYMEM_MEM) && dwOutBufLen==sizeof(PVOID64) ) { PHYSICAL_ADDRESS phyAddr; PVOID64 pvk, pvu; phyAddr.QuadPart=(ULONGLONG)pMem->pvAddr; ...以下省略
總結
不要以為把程式拿來編譯成64-bit就會動了,不同的component溝通、相依於作業系統的功能、作業系統特性等,都有可能出現難以解決的bug。另外,你們可能會有疑問的是: 我是如何發現這個問題的? 答案是透過Kernel Driver Debug工具: DebugPrint。等我有時間會在教大家如何使用,因為我花在編譯與安裝成64-bit也花了不少時間。
阿兩內心的獨白: 因為這個玩意兒,讓我第一次去改windows driver的code。
留言
張貼留言