作 者: mickeylan
時 間: 2008-01-10,21:16
鏈 接: http://bbs.pediy.com/showthread.php?t=58070
上篇教程主要是講解了用Delphi開發Windows驅動程序需要解決的一些技術上的問題,雖然啰嗦了一大堆,也不知道講清楚了沒有^_^。本篇我們開始講述用Delphi構建驅動開發環境。
用Delphi開發驅動程序所必須的工具:
 Dcc32.exe – Delphi編譯器,我用的是Delphi 2007的dcc32
 rmcoff -- 我用BCB開發的Delphi目標文件符號名修改、OMF到coff格式轉換以及刪除obj文件中無用代碼及重復符號的工具
 Link.exe -- microsoft鏈接器,不要使用7.1xx版的,似乎有bug
 DDK相關結構、APIs的Delphi聲明文件(我已經完成部分結構、APIs的聲明轉換,放在我的KmdKit4D工具包裡)
有上面的東東就可以開發Windows驅動程序了,下面就讓我們來寫一個最簡單的驅動程序:
代碼:unit driver;
interface
uses nt_status, ntoskrnl;
function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;
implementation
procedure DriverUnload(DriverObject:PDriverObject); stdcall;
begin
DbgPrint(DriverUnload(DriverObject:0x%.8X),[DriverObject]);
DbgPrint(DriverUnload(-),[]);
end;
function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;
begin
DbgPrint(DriverEntry(DriverObject:0x%.8X;RegistryPath:0x%.8X),[DriverObject,RegistryPath]);
DriverObject^.DriverUnload:=@DriverUnload;
Result:=STATUS_SUCCESS;
DbgPrint(DriverEntry(-):0x%.8X,[Result]);
end;
end.
以上就是一個最簡單的驅動程序,就像其他的可執行程序一樣,每個驅動程序也有一個入口點,這是當驅動被裝載到內存中時首先被調用的,驅動的入口點是DriverEntry過程(注:過程也就是子程序),DriverEntry這個名稱只是一個標記而已,你可以把它命名為其他任何名字--只要它是入口點就行了。DriverEntry過程用來對驅動程序的一些數據結構進行初始化,它的函數原型定義如下:
代碼:function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;
當然你也可以不用DriverEntry這個名字,任意的名字都可以,不過前面的下劃線是必需的。nt_status和 ntoskrnl兩個單元包含了常用的數據結構和APIs的聲明。由於我常開發Unix下的程序,所以我習慣使用make編譯程序,個人感覺make比較智能和方便,因此在推薦大家使用make編譯程序。我用的是borland make 5.2版。Makefile的寫法可以參考http://bbs.pediy.com/showthread.php?t=56912,以下是編譯這個程序的makefile:
代碼:NAME=driver
DCC=dcc32
INCLUDE=d:mickeylanKmdKit4Dinclude
LIB_PATH=d:mickeylanKmdKit4Dlib
DCCFLAGS=-U$(INCLUDE) -B -CG -JP -$A-,C-,D-,G-,H-,I-,L-,P-,V-,W+,Y-
LIBS=ntoskrnl.lib hal.lib win32k.lib ntdll.lib
LINKFLAGS=/NOLOGO /ALIGN:32 /BASE:0x10000 /SUBSYSTEM:NATIVE /DRIVER /LIBPATH:$(LIB_PATH) /FORCE:UNRESOLVED /FORCE:MULTIPLE /ENTRY:DriverEntry
all : $(NAME).sys
$(NAME).sys : $(NAME).obj
omf2d $(NAME).obj /U_*
link $(LINKFLAGS) $(LIBS) /out:$(NAME).sys $(NAME).obj
$(NAME).obj : $(NAME).pas
$(DCC) $(DCCFLAGS) $(NAME).pas
clean :
del *.obj
del *.dcu
del *.sys
在命令行下執行make即可編譯生成驅動文件,是不是很簡單^_^。此程序的源碼放在KmdKit4D的sampleasic目錄下,該目錄下還有一個loaddriver.bat,執行此批處理文件即可加載驅動,並且可以在DbgView的窗口裡看見驅動程序輸出的調試信息。
到這裡,你應該對用Delphi開發驅動程序有了個大體的了解了,下面讓我們再來寫一個很有趣的驅動程序以加深了解。這個程序是從Four-F的KmdKit的giveio轉換來的(我比較懶,不想寫新的^_^),寫個驅動程序讓用戶模式下的進程能通過讀取端口來訪問電腦的CMOS。
大家都知道,端口是被Windows保護起來的,正常情況下,用戶模式下的程序是無法直接操作端口的,通過我們的驅動程序修改I/O許可位圖(I/O permission bit map,IOPM),這樣用戶模式下的相應進程就被允許自由地存取I/O端口,這方面詳細資料見http://www.intel.com/design/intarch/techinfo/pentium/PDF/inout.pdf。每個進程都有自己的I/O許可位圖,每個單獨的I/O端口的訪問權限都可以對每個進程進行單獨授權,如果相關的位被設置的話,對對應端口的訪問就是被禁止的,如果相關的位被清除,那麼進程就可以訪問對應的端口。既然I/O地址空間由64K個可單獨尋址的8位I/O端口組成,IOPM表的最大尺寸就是2000h字節(注:每個端口的權限用1個bit表示,64K個端口除以8得到的就是IOPM的字節數,也就是65536/8=8192字節=2000h字節)。
TSS的設計意圖是為了在任務切換的時候保存處理器狀態,從執行效率的考慮出發,Windows NT並沒有使用這個特征,它只維護一個TSS供多個進程共享,這就意味著IOPM也是共享的,因此某個進程改變了IOPM的話,造成的影響是系統范圍的。
ntoskrnl.exe中有些未公開的函數是用來維護IOPM的,它們是Ke386QueryIoAccessMap和Ke386SetIoAccessMap函數。
代碼:function Ke386QueryIoAccessMap(
dwFlag:DWORD;
pIopm:PVOID): NTSTATUS; stdcall;
Ke386QueryIoAccessMap函數從TSS中拷貝2000h字節的當前IOPM到指定的內存緩沖區中,緩沖區指針由pIopm參數指定。
各參數描述如下:
◎ dwFlag--0表示將全部緩沖區用0FFh填寫,也就是所有的位都被設置,所有的端口都被禁止訪問;1表示從TSS中將當前IOPM拷貝到緩沖區中
◎ pIopm--用來接收當前IOPM的緩沖區指針,注意緩沖區的大小不能小於2000h字節
如果函數執行成功的話會在返回值的低8位返回非0值;如果執行失敗則返回零。
代碼:function Ke386SetIoAccessMap(
dwFlag:DWORD;
pIopm:PVOID): NTSTATUS; stdcall;
Ke386SetIoAccessMap函數剛好相反,它從pIopm參數指定的緩沖區中拷貝2000h字節的IOPM到TSS中去。
各參數描述如下:
◎ dwFlag--這個參數只能是1,其他任何值函數都會返回失敗
◎ pIopm--指向包含IOPM數據的緩沖區,緩沖區的尺寸不能小於2000h字節
如果函數執行成功的話會在返回值的低8位返回非0值;如果執行失敗則返回零
當IOPM拷貝到TSS後,IOPM的偏移指針必須被定位到新的數據中去,這可以通過Ke386IoSetAccessProcess函數來完成,這也是ntoskrnl.exe中的一個很有用的未公開函數。
代碼:function Ke386IoSetAccessProcess(
pProcess: PKPROCESS;
dwFlag:DWORD): NTSTATUS; stdcall;
Ke386IoSetAccessProcess允許或者禁止對進程使用IOPM。其參數說明如下:
◎ pProcess--指向KPROCESS結構
◎ dwFlag--0表示禁止對I/O端口進行存取,將IOPM的偏移指針指到TSS段外面;1表示允許存