zoukankan      html  css  js  c++  java
  • 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, 
    00nil);
      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 
    <> 0then
        
    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
  • 相关阅读:
    文件上传-pubsec-文件上传大小限制
    编写 .gitignore 文件
    Git 创建点开头的文件和目录
    Git 克隆远程仓库到本地
    redis 在 windows 中的安装
    查看数据库字符集和排序规则
    centos 6 和centos 7 系统下vnc配置
    centos 6 下KVM 安装学习之旅
    Centos 下使用VLAN+Bridge 搭建KVM基础网络环境
    centos 6 KVM 网卡桥接配置
  • 原文地址:https://www.cnblogs.com/sonicit/p/1152990.html
Copyright © 2011-2022 走看看