Delphi研究之驱动开发篇(二)

上篇教程主要是讲解了用Delphi开发Windows驱动程序需要解决的一些技术上的问题,虽然啰嗦了一大堆,也不知道讲清楚了没有^_^。本篇我们开始讲述用Delphi构建驱动开发环境。
用Delphi开发驱动程序所必须的工具:
  Dcc32.exe – Delphi编译器,我用的是Delphi 2007的dcc32
  Omf2d   -- Delphi目标文件转换工具
  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:\mickeylan\KmdKit4D\include
LIB_PATH
= d:\mickeylan\KmdKit4D\lib
DCCFLAGS
=- U$(INCLUDE)  - - 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;
      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表示允许存取I/O端口,将IOPM的偏移指针指到TSS段的88h中
    如果函数执行成功的话会在返回值的低8位返回非0值;如果执行失败则返回零
    顺便提一下,ntoskrnl中的所有函数都有前缀,通过这个前缀你就可以辨别该函数属于系统功能中的哪一类。不同的前缀表示不同的功能--如i前缀表示内部使用(internal)、p表示私有函数(private)、f表示fastcall。再如,Ke表示内核函数(kernel),Psp表示内部进程支持函数(internal process support),Mm表示内存管理函数(Memory Manager)等等。
    Ke386IoSetAccessProcess函数的第一个参数指向进程对象,也就是KPROCESS结构(在\include\nt_status.dcu中定义),Ke386IoSetAccessProcess会将KPROCESS结构中IopmOffset字段的值设置为合适的值。

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  <>   nil   then
      
begin
        
if  PsLookupProcessByProcessId(kvpi.dData, pProcess)  =  STATUS_SUCCESS  then
        
begin
          DbgPrint(
' giveio: PTR KPROCESS: %08X ' , [@pProcess]);
          iRet :
=  Ke386QueryIoAccessMap( 0 , pIopm);
          
if  iRet  and  $ff  <>   0   then
          
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  <>   0   then
            
begin
              iRet :
=  Ke386IoSetAccessProcess(pProcess,  1 );
              
if  iRet  and  $FF  <>   0   then
              
begin
                DbgPrint(
' giveio: I/O permission is successfully given ' ,[]);
              
end   else
              
begin
                DbgPrint(
' giveio: I/O permission is failed ' ,[]);
                status :
=  STATUS_IO_PRIVILEGE_FAILED;
              
end ;
            
end   else
            
begin
              status :
=  STATUS_IO_PRIVILEGE_FAILED;
            
end ;
          
end   else
          
begin
            status :
=  STATUS_IO_PRIVILEGE_FAILED;
          
end ;
          ObfDereferenceObject(pProcess);
        
end   else
        
begin
          status :
=  STATUS_OBJECT_TYPE_MISMATCH;
        
end ;
        MmFreeNonCachedMemory(pIopm, IOPM_SIZE);
      
end   else
      
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)  - - 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
  通过上面的两个例子的学习,相信大家已经能用Delphi写些基本的驱动程序了。本教程的第二部分也就到此为止了,后面还会有更精彩的内容。

  PS:随本教程我发布了我的KmdKit4D 0.01预览版,目前只完成了万里长征的第一步,后面的路还很长,希望有兴趣的朋友能和我一起努力来走完剩下的路^_^
  
  下载: KmdKit4D.rar
作 者: mickeylan 转至: http://bbs.pediy.com/showthread.php?t=58070

转载于:https://www.cnblogs.com/sonicit/archive/2008/04/14/1152990.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值