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:\mickeylan\KmdKit4D\include LIB_PATH=d:\mickeylan\KmdKit4D\lib 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的sample\basic目录下,该目录下还有一个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;
unit giveio; interface uses nt_status, ntoskrnl, ntutils; const IOPM_SIZE = $2000; function _DriverEntry(DriverObject:PDriverObject;pusRegistryPath:PUnicodeString):NTSTATUS; stdcall; implementation function _DriverEntry(DriverObject:PDriverObject;pusRegistryPath:PUnicodeString):NTSTATUS; stdcall; var status:NTSTATUS; oa:OBJECT_ATTRIBUTES; hKey:HANDLE; kvpi:KEY_VALUE_PARTIAL_INFORMATION; pIopm:PVOID; pProcess: PVOID; iRet: NTSTATUS; resultLen: ULONG; KeyValue: TUnicodeString; begin DbgPrint('giveio: Entering DriverEntry',[]); status := STATUS_DEVICE_CONFIGURATION_ERROR; InitializeObjectAttributes(oa, pusRegistryPath, 0, 0, nil); iRet := ZwOpenKey(hKey, KEY_READ, @oa); if iRet = STATUS_SUCCESS then begin RtlInitUnicodeString(KeyValue, 'ProcessId'); if (ZwQueryValueKey(hKey, @KeyValue, KeyValuePartialInformation, PVOID(@kvpi), sizeof(kvpi), resultLen) <> STATUS_OBJECT_NAME_NOT_FOUND) and (resultLen <>0) then begin DbgPrint('giveio: Process ID: %X', [kvpi.dData]); {Allocate a buffer for the I/O permission map} pIopm := MmAllocateNonCachedMemory(IOPM_SIZE); if pIopm <>nilthen begin if PsLookupProcessByProcessId(kvpi.dData, pProcess) = STATUS_SUCCESS then begin DbgPrint('giveio: PTR KPROCESS: %08X', [@pProcess]); iRet := Ke386QueryIoAccessMap(0, pIopm); if iRet and $ff <>0then begin {I/O access for 70h port} asm pushad mov ecx, pIopm add ecx, 70h /8 mov eax, [ecx] btr eax, 70h MOD 8 mov [ecx], eax {I/O access for 71h port} mov ecx, pIopm add ecx, 71h /8 mov eax, [ecx] btr eax, 71h MOD 8 mov [ecx], eax popad end; iRet := Ke386SetIoAccessMap(1, pIopm); if iRet and $FF <>0then begin iRet := Ke386IoSetAccessProcess(pProcess, 1); if iRet and $FF <>0then begin DbgPrint('giveio: I/O permission is successfully given',[]); endelse begin DbgPrint('giveio: I/O permission is failed',[]); status := STATUS_IO_PRIVILEGE_FAILED; end; endelse begin status := STATUS_IO_PRIVILEGE_FAILED; end; endelse begin status := STATUS_IO_PRIVILEGE_FAILED; end; ObfDereferenceObject(pProcess); endelse begin status := STATUS_OBJECT_TYPE_MISMATCH; end; MmFreeNonCachedMemory(pIopm, IOPM_SIZE); endelse begin DbgPrint('giveio: Call to MmAllocateNonCachedMemory failed',[]); status := STATUS_INSUFFICIENT_RESOURCES; end; end; ZwClose(hKey); end; DbgPrint('giveio: Leaving DriverEntry',[]); result := status; end; end.
以下是makefile:
NAME=giveio DCC=dcc32 INCLUDE=e:\mickeylan\KmdKit4D\include LIB_PATH=e:\mickeylan\KmdKit4D\lib 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 ntutils.obj $(NAME).obj : $(NAME).pas $(DCC) $(DCCFLAGS) $(NAME).pas clean : del *.obj del *.dcu del *.sys