2011-08-26 15:38:05 sustzw 阅读数 1289
  • DelphiXE10零基础实战快速入门

    通过课堂上一个一个的实战例子,演示DelphiXE10的用法,最后通过一个完整的通讯录程序,让学员初步掌握软件开发的全部流程 让零基础学员快速入门Delphi编程,快速掌握Delphi的使用方法,并能够制作出自己的软件来。为同学们下一步的提高打下坚实的基础。

    1044 人正在学习 去看看 陈城
 (注:本篇的原理部分均摘自罗云彬大侠翻译的驱动开发教程)
     在前面的两篇教程中我们写了三个玩具驱动程序,为什么说是玩具驱动呢?因为它们确确实实是驱动程序,而且也能完成一些有趣的功能,但是它们都不完整,没有同用户交流的功能,这一篇就让我们来完成一个简单的全功能驱动程序。
   在写程序之前,我们有必要了解一些基础知识。
   在用户模式下,我们可以通过访问某个地址来直接调用dll中的函数,但是在内核模式下,从系统的稳定性考虑,这样做是非常危险的。所以,系统提供了和内核模式通讯的媒介--I/O管理器,它是I/O子系统的部件之一。I/O管理器将应用程序、系统部件和设备连接起来,并定义了一个架构来支持设备驱动程序。下图是I/O管理器如何在用户模式程序和驱动程序之间进行沟通的简单图解。
screen.width-333)this.width=screen.width-333" dypop="按此在新窗口浏览图片">
    一般来说,用户模式的操作都被转换成了对具体硬件设备的I/O操作,仅对于某些设备,设备由驱动程序来创建和控制,这些设备就是虚拟设备。当然,创建这些设备并不意味着你创造了什么硬件,而仅仅是在内存中创建了一个新的对象而已。每个对象和一个物理设备或者逻辑设备对应,用于描述它们的特征。
   创建设备后,驱动程序告诉I/O管理器:“这里有个我控制的设备,如果你收到了操作这个设备的I/O请求的话,直接发给我好了,剩下的由我来搞定!”。驱动程序知道如何对自己管理的设备进行I/O操作,I/O管理器唯一的职责在于创建I/O请求并把它发送给适当的设备驱动程序。用户模式的代码不知道(也不必知道)其中的细节,也不用知道究竟是哪个驱动程序在管理哪个设备。
下面先让我们来看一下用户模式下的控制程序:
program VirToPhys;

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows, WinSvc, Dialogs, nt_status;

const
  NUM_DATA_ENTRY =4;
  DATA_SIZE = sizeof(DWORD) * NUM_DATA_ENTRY;
  _DELETE = $10000;

var
  hSCManager:THANDLE;
  hService:THANDLE;
  acModulePath: array [0..MAX_PATH] of char;
  _ss:SERVICE_STATUS;
  hDevice:THANDLE;

  adwInBuffer: array [0..NUM_DATA_ENTRY] of DWORD;
  adwOutBuffer: array [0..NUM_DATA_ENTRY] of DWORD;
  dwBytesReturned:DWORD;
  IOCTL_GET_PHYS_ADDRESS: DWORD;
  lpTemp: PChar;
  iRetValue: boolean;

{生成控制码}  
function CTL_CODE(DeviceType, Func, Method, Access: DWORD): DWORD;
begin
  result := (((DeviceType) SHL 16) OR ((Access) SHL 14) OR ((Func) SHL 2) OR (Method));
end;

begin
  IOCTL_GET_PHYS_ADDRESS := CTL_CODE(FILE_DEVICE_UNKNOWN,
                            $800, METHOD_BUFFERED,
                            FILE_READ_ACCESS + FILE_WRITE_ACCESS);
  hSCManager := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
  if hSCManager <> 0 then
  begin
    GetFullPathName(PChar('VirtToPhys.sys'), sizeof(acModulePath), acModulePath, lpTemp);
    hService := CreateService(hSCManager, 'VirtToPhys', 'Virtual To Physical Address Converter',
                              SERVICE_START + SERVICE_STOP + _DELETE, SERVICE_KERNEL_DRIVER,
                              SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, acModulePath,
                              nil, nil, nil, nil, nil);
    if hService <> 0 then
    begin
      {驱动程序的DriverEntry过程将被调用}
      if StartService(hService, 0, lpTemp) = true then
      begin
        {驱动程序将接收IRP_MJ_CREATE I/O请求包(IRP)}
        hDevice := CreateFile('\\.\slVirtToPhys', GENERIC_READ+GENERIC_WRITE,
                              0, nil, OPEN_EXISTING, 0, 0);
        if hDevice <> INVALID_HANDLE_VALUE  then
        begin
          {准备送往驱动程序的数据包}
          adwInBuffer[0] := GetModuleHandle(nil);
          adwInBuffer[1] := GetModuleHandle('kernel32.dll');
          adwInBuffer[2] := GetModuleHandle('user32.dll');
          adwInBuffer[3] := GetModuleHandle('comctl32.dll');
          {驱动程序将接收IRP_MJ_DEVICE_CONTROL I/O请求包}
          iRetValue := DeviceIoControl(hDevice, IOCTL_GET_PHYS_ADDRESS,
                                       @adwInBuffer, sizeof(adwInBuffer),
                                       @adwOutBuffer, sizeof(adwOutBuffer),
                                       dwBytesReturned, nil);
          if (iRetValue = true) and (dwBytesReturned <> 0) then
          begin
            {取程序名}
            GetModuleFileName(adwInBuffer[0], acModulePath, sizeof(acModulePath));
            ShowMessage(Format('Modules          BASE     Physical'#13#10 +
                               '----------------------------------'#13#10 +
                               '%s    %8.8X %8.8X'#13#10 +
                               'kernel32.dll     %8.8X %8.8X'#13#10 +
                               'user32.dll       %8.8X %8.8X'#13#10 +
                               'comctl32.dll     %8.8X %8.8x',
                               [ExtractFileName(acModulePath), adwInBuffer[0], adwOutBuffer[0],
                                adwInBuffer[1], adwOutBuffer[1],
                                adwInBuffer[2], adwOutBuffer[2],
                                adwInBuffer[2], adwOutBuffer[2]]));
          end else
          begin
            ShowMessage('Can''t send control code to device.');
          end;
          {Driver will receive IRP of type IRP_MJ_CLOSE}
          CloseHandle(hDevice);
        end else
        begin
          ShowMessage('Device is not present.');
        end;
        {DriverUnload proc in our driver will be called}
        ControlService(hService, SERVICE_CONTROL_STOP, _ss);
      end else
      begin
        ShowMessage('Can''t start driver.');
      end;
      DeleteService(hService);
      CloseServiceHandle(hService);
    end else
    begin
      ShowMessage('Can''t register driver.');
    end;
  CloseServiceHandle(hSCManager);
  end else
  begin
    ShowMessage('Can''t connect to Service Control Manager.');
  end;
end.

      这个代码包括了注册和启动驱动程序,以及作为客户端程序和设备进行通讯的代码。代码将输入的数据发送给设备,并将设备返回的数据格式化并显示出来。
    驱动被装载后,VirtToPhys驱动程序创建了一个名为"devVirtToPhys"的设备,内部设备名称无法被Win32应用程序使用,因此,如果我们的驱动程序对应的设备对象希望被用户模式的代码打开的话,就必须在"\??"目录中创建一个符号连接,指向"\Device"目录中的设备对象,然后,当调用者需要获取设备句柄时,I/O管理器就能够找到它。
    VirtToPhys驱动程序在"\??"目录中创建了指向"devVirtToPhys"设备的符号连接"slVirtToPhys",真实设备的全名是"\Device\devVirtToPhys",这样,当StartService函数执行后,系统中就多了三个新的对象:"\Driver\VirtToPhys"驱动、"\Device\devVirtToPhys"设备和符号连接"\??\slVirtToPhys"。
    现在来看看控制程序源代码,当驱动被启动后,我们只需要使用CreateFile函数来打开驱动,以此获得一个文件句柄。函数原型如下:
function CreateFile(lpFileName: PChar; 
                    dwDesiredAccess, dwShareMode: DWORD;
                    lpSecurityAttributes: PSecurityAttributes;dwCreationDisposition, dwFlagsAndAttributes: DWORD;
                    hTemplateFile: THandle): THandle; stdcall;

这个函数可以创建或者打开一个已存在的对象,而不仅仅是文件。函数的参数描述如下:
◎ lpFileName--指向以0结尾的表示设备名称的字符串,这里要用到指向设备对象的符号连接名
◎ dwDesiredAccess--指定访问设备的方式,可以有两个取值:GENERIC_READ表示写操作,允许将数据写到设备中;GENERIC_WRITE表示读操作,允许从设备读取数据,这两个值可以合并起来使用
◎ dwShareMode--指定设备是否可以被共享,0表示设备不能被共享,这样并发的访问会失败,直到句柄被关闭为止;要共享设备的话,可以指定下面的两个值:FILE_SHARE_READ表示可以并发地读取设备,FILE_SHARE_WRITE表示可以并发地写设备
◎ lpSecurityAttributes--指向SECURITY_ATTRIBUTES结构的指针,在此无用,所以可以指定为NULL
◎ dwCreationDistribution--指明当文件存在或不存在时函数采取的动作,对于设备来说,这个参数应该使用OPEN_EXISTING
◎ dwFlagsAndAttributes--文件属性,在这里总是使用0
◎ hTemplateFile--指定文件模板的句柄,在这里总是使用0

    如果CreateFile函数成功地创建或者打开了指定的设备,那么返回值就是设备句柄,否则返回值是INVALID_HANDLE_VALUE。我们这样使用CreateFile函数:
hDevice := CreateFile('\\.\slVirtToPhys', GENERIC_READ+GENERIC_WRITE,0, nil, OPEN_EXISTING, 0, 0);

    来看看第一个参数,这是指向符号连接名称的字符串指针,名称格式是"\\.\slVirtToPhys","\\.\"是Win32中定义本地计算机的方法,CreateFile函数实际上是NtCreateFile函数的封装(位于\%SystemRoot%\System32\ntdll.dll中),后者将访问定向到系统服务中去(注意不要和Win32服务进程混淆)。
    NtCreateFile将本地计算机的别名"\\.\"用"\??"代替(这样\\.\slVirtToPhys就变成了\?? \slVirtToPhys)并调用内核的ObOpenObjectByName函数,通过符号连接名称,ObOpenObjectByName函数找到\Device\devVirtToPhys对象并返回它的指针。NtCreateFile使用这个指针创建新的文件对象并返回句柄。
    操作系统把所有的I/O操作请求抽象化成一个虚拟的文件,隐藏了目标设备的I/O操作可能在结构上并不等同于文件的事实,然后由驱动程序负责将对虚拟文件的请求转换成对具体硬件的请求,这种抽象可以推广到所有用户进程和设备之间的界面,所有对这种虚拟文件的读写操作都被当作是简单的流操作来处理。
    在CreateFile返回前,I/O管理器创建了IRP_MJ_CREATE类型的IRP并将其传给驱动处理,驱动中负责响应这个IRP的子程序代码会在发起I/O请求(就是调用CreateFile函数的代码)的线程环境中执行,该线程的IRQL等于PASSIVE_LEVEL。如果驱动的子程序成功返回,那么对象管理器在进程的句柄表中为文件对象创建一个句柄,然后将其一层层地返回,直到返回到CreateFile函数。
    现在来总结一下,"\\.\slVirtToPhys"会被转换成符号连接"\??\slVirtToPhys",最终用来找到需要的"\Device\devVirtToPhys"设备。然后可以从负责维护该设备的驱动程序中取得设备对象DEVICE_OBJECT,接下来I/O管理器将IRP_MJ_CREATE请求传递给驱动,驱动程序知道如何处理这个请求。如果驱动打算处理该请求,那么它返回成功代码,这样对象管理器创建对应这个设备的虚拟文件句柄并将它返回给用户模式代码。
    句柄和符号连接为间接访问系统资源提供服务,这种"间接"方式将应用程序和系统的数据结构隔离开来。
    CreateFile函数返回有效的设备句柄后,我们将它保存在hDevice变量中,现在可以用ReadFile、WriteFile以及DeviceIoControl函数来和设备通讯了。DeviceIoControl函数是用来和设备通讯的通用函数,它的原型如下:
function DeviceIoControl(hDevice: THandle; 
    dwIoControlCode: DWORD; 
    lpInBuffer: Pointer;          
    nInBufferSize: DWORD; 
    lpOutBuffer: Pointer;  
    nOutBufferSize: DWORD;
    var lpBytesReturned: DWORD; 
    lpOverlapped: POverlapped): BOOL; stdcall;

    DeviceIoControl函数的参数比CreateFile多,但用起来都很简单。
   ◎ hDevice--设备的句柄 
   ◎ dwIoControlCode--控制代码,指出要进行什么操作,详细内容后面再做解释
   ◎ lpInBuffer--指向包含操作所需的数据的缓冲区指针,如果控制代码指明的操作并不需要输入数据的话,这里可以用NULL
     ◎ nInBufferSize--lpInBuffer参数指向的缓冲区的大小
   ◎ lpOutBuffer--指向用来接收输出数据的缓冲区,如果dwIoControlCode指明的操作不产生输出数据的话,这里可以用NULL
    ◎ nOutBufferSize--lpOutBuffer参数指向的缓冲区的大小
   ◎ lpBytesReturned--指向一个变量,用来返回放入lpOutBuffer缓冲区的数据的数量
   ◎ lpOverlapped--指向OVERLAPPED结构,这个参数仅在异步操作的时候才需要。我们的操作是同步的(就是在驱动的过程返回前DeviceIoControl函数也不返回),所以在这里使用NULL
    设备驱动程序可以被当作内核模式函数包来看待,I/O控制代码就是用来指定访问其中的哪个函数的。DeviceIoControl函数的dwIoControlCode参数就是这个代码,它指出了我们需要进行的操作,以及如何进行操作。
控制代码是32位数字型常量,可以CTL_CODE函数来生成。控制代码的组成见下图:
按此在新窗口浏览图片screen.width-333)this.width=screen.width-333"> 
    控制代码中各数据位字段的含义如下:
◎ DeviceType--设备类型(16bit)指出了设备的类型,微软保留了0-7FFFh的取值,剩下的8000h-0FFFFh供开发商定义新的内核模式驱动程序。我们可以在\include\w2k\ntddk.inc文件中找到一组FILE_DEVICE_XXX符号常量,这些值都是微软保留的值,我们可以使用其中的FILE_DEVICE_UNKNOWN。当然你也可以定义另外一个FILE_DEVICE_XXX值
◎ Access--存取代码(2bit)指明应用程序存取设备的方式,由于这个字段只有2位,所以只有4种可能性:
? FILE_ANY_ACCESS (0)--最大的存取权限,就是什么操作都可以
? FILE_READ_ACCESS (1)--读权限,设备将数据传递到指定的缓冲区
? FILE_WRITE_ACCESS (2)--写权限,可以从内存中向设备传递数据
? FILE_READ_ACCESS or FILE_WRITE_ACCESS (3)--读写权限,设备和内存缓冲区之间可以互相传递数据
◎ Function--功能代码(12bit)用来描述要进行的操作,我们可以用800h-0FFFh来定义自己的I/O控制代码,0-7FFh之间的值是被微软保留的,用来定义公用的I/O控制代码
◎ Method--缓冲模式(2bit)表示I/O管理器如何对输入和输出的数据进行缓冲,这个字段的长度是2位,所以有4种可能性:
? METHOD_BUFFERED (0)--对I/O进行缓冲
? METHOD_IN_DIRECT (1)--对输入不进行缓冲
? METHOD_OUT_DIRECT (2)--对输出不进行缓冲
? METHOD_NEITHER (3)--都不缓冲

    虽然进行缓冲会带来一些额外的内存开销,但却是最安全的,因为系统已经做好了相关的全部工作。在传输的数据小于一页(4Kb)的时候,驱动程序通常使用缓冲方式的I/O,因为对大量小块内存进行内存锁定带来的开销也是很大的。在VirtToPhys驱动程序中,我们使用带缓冲的方式。
    我们在adwInBuffer缓冲区中填写需要进行转换的虚拟地址。
   
 adwInBuffer[0] := GetModuleHandle(nil);
      adwInBuffer[1] := GetModuleHandle('kernel32.dll');
      adwInBuffer[2] := GetModuleHandle('user32.dll');
      adwInBuffer[3] := GetModuleHandle('comctl32.dll');
      
    然后调用DeviceIoControl将缓冲区传给驱动程序,驱动程序会将虚拟地址转换成物理地址。如果DeviceIoControl函数成功返回,那么dwBytesReturned中的数值就等于驱动程序在adwOutBuffer缓冲区中返回的数据的长度,然后我们只要把返回值格式化一下并显示出来就可以了。
最后就是扫尾工作,调用CloseHandle关闭设备句柄。这时I/O管理器向设备驱动程序发送两个IRP,第一个是IRP_MJ_CLEANUP,它告诉驱动程序设备句柄将要被关闭了;然后是IRP_MJ_CLOSE,它告诉驱动程序设备句柄已经被关闭了。你可以在收到IRP_MJ_CLEANUP时返回一个错误代码,这样就可以阻止设备句柄被关闭。驱动程序的子程序在处理这些IRP时,代码都是在发出I/O请求的线程环境中执行的(也就是调用CloseHandle的线程),它们的IRQL = PASSIVE_LEVEL。
以上就是用户模式下的控制程序了,下面来看看我们的驱动程序。
unit VirtToPhys;

interface

uses
  nt_status, ntoskrnl, ntutils;

function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall;

implementation

const
  NUM_DATA_ENTRY = 4;
  DATA_SIZE = sizeof(DWORD) * NUM_DATA_ENTRY;

var
  g_usDeviceName, g_usSymbolicLinkName: UNICODE_STRING;

function DispatchCreateClose(p_DeviceObject:PDEVICE_OBJECT; p_Irp:PIRP): NTSTATUS; stdcall;
begin
  p_Irp^.IoStatus.Status := STATUS_SUCCESS;
  p_Irp^.IoStatus.Information := 0;

  IofCompleteRequest(p_Irp, IO_NO_INCREMENT);
  result := STATUS_SUCCESS;
end;

function DispatchControl(p_DeviceObject: PDEVICE_OBJECT; p_Irp:PIRP): NTSTATUS; stdcall;
var
  status:NTSTATUS;
  dwBytesReturned:DWORD;
  psl:PIO_STACK_LOCATION;
  IOCTL_GET_PHYS_ADDRESS: DWORD;
  pSystemBuffer: PULONG;
  iCnt: DWORD;
  liPhysicalAddress: PHYSICAL_ADDRESS;
begin
  dwBytesReturned := 0;

  IOCTL_GET_PHYS_ADDRESS := CTL_CODE(FILE_DEVICE_UNKNOWN,
    $800, METHOD_BUFFERED, FILE_READ_ACCESS + FILE_WRITE_ACCESS);


  psl := IoGetCurrentIrpStackLocation(p_Irp);

  if psl^.Parameters.DeviceIoControl.IoControlCode = IOCTL_GET_PHYS_ADDRESS then
  begin
    if (psl^.Parameters.DeviceIoControl.OutputBufferLength >= DATA_SIZE)
       and (psl^.Parameters.DeviceIoControl.InputBufferLength >= DATA_SIZE) then
    begin
      pSystemBuffer := p_Irp^.AssociatedIrp.SystemBuffer;
      iCnt := 0;
      for iCnt := 1 to NUM_DATA_ENTRY do
      begin
        liPhysicalAddress := MmGetPhysicalAddress(PVOID(pSystemBuffer^));
        pSystemBuffer^ := liPhysicalAddress.LowPart;
        inc(pSystemBuffer);
      end;
      dwBytesReturned := DATA_SIZE;
      status := STATUS_SUCCESS;
    end else
    begin
      status := STATUS_BUFFER_TOO_SMALL;
    end;
  end else
  begin
    status := STATUS_INVALID_DEVICE_REQUEST;
  end;
  
  p_Irp^.IoStatus.Status := status;
  p_Irp^.IoStatus.Information := dwBytesReturned;

  IofCompleteRequest(p_Irp, IO_NO_INCREMENT);
  result := status;
end;

procedure DriverUnload(p_DriverObject:PDRIVER_OBJECT); stdcall;
begin
  IoDeleteSymbolicLink(@g_usSymbolicLinkName);
  IoDeleteDevice(p_DriverObject^.DeviceObject);
end;


function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS;
var
  status:NTSTATUS;
  pDeviceObject:TDeviceObject;
begin
  status := STATUS_DEVICE_CONFIGURATION_ERROR;
  RtlInitUnicodeString(@g_usDeviceName, '\Device\devVirtToPhys');
  RtlInitUnicodeString(@g_usSymbolicLinkName, '\??\slVirtToPhys');

  if (IoCreateDevice(pDriverObject, 0, @g_usDeviceName,
                     FILE_DEVICE_UNKNOWN, 0, FALSE,
                     pDeviceObject) = STATUS_SUCCESS) then
  begin
    if (IoCreateSymbolicLink(@g_usSymbolicLinkName,
                             @g_usDeviceName) = STATUS_SUCCESS) then
    begin
      pDriverObject^.MajorFunction[IRP_MJ_CREATE] := @DispatchCreateClose;
      pDriverObject^.MajorFunction[IRP_MJ_CLOSE] := @DispatchCreateClose;
      pDriverObject^.MajorFunction[IRP_MJ_DEVICE_CONTROL] := @DispatchControl;
      pDriverObject^.DriverUnload := @DriverUnload;

      status := STATUS_SUCCESS;
    end else
    begin
      IoDeleteDevice(@pDeviceObject);
    end;
  end;
  result := status;
end;

end.

  程序很简单,所以一行注释也没加^_^。既然驱动程序的主要作用是用于控制一些设备,包括物理设备、虚拟设备或者逻辑设备,那么我们首先必须将这些设备创建起来(本例中是虚拟设备),这可以通过调用IoCreateDevice函数来完成,函数将创建并初始化一个由驱动程序使用的设备对象(DEVICE_OBJECT结构),其原型如下:
function IoCreateDevice(DriverObject: PDRIVER_OBJECT;
                        DeviceExtensionSize: ULONG;
                        DeviceName: PUNICODE_STRING;
                        DeviceType: DEVICE_TYPE;
                        DeviceCharacteristics: ULONG;
                        Exclusive: BOOLEAN;
                        var DeviceObject:TDeviceObject):NTSTATUS; stdcall;
  
函数的参数描述如下:
◎ DriverObject--指向驱动对象(DRIVER_OBJECT结构),每个驱动程序在DriverEntry过程中会通过参数收到一个指向它的驱动对象的指针
◎ DeviceExtensionSize--指定设备扩展结构的大小(注:I/O管理器将自动分配这个内存,并把设备对象中的DeviceExtension指针指向这块内存),扩展结构的数据结构定义由驱动程序自己决定,我们的驱动太简单了,就没必要使用这个了
◎ DeviceName--指向一个Unicode字符串,用来指定设备名称,该名称必须是全路径的名称,在这里路径的含义并不是指硬盘上的路径,而是指对象管理器命名空间中的路径。这个参数在本例中是必须的,因为我们必须创建一个命名的设备,否则就无法创建一个符号连接,那样用户模式的进程也就无法访问设备了。设备名称在系统中必须是唯一的(注:在其他的应用中,你也可以创建不命名的设备)
◎ DeviceType--在系统定义的FILE_DEVICE_XXX常数中选定一个,用于指定设备的类型,当然也可以使用自定义的类型来表示一个新的类别,这里我们使用FILE_DEVICE_UNKNOWN
◎ DeviceCharacteristics--指明设备的额外属性,本例中使用0
◎ Exclusive--指明设备对象是否必须被独占使用,也就是说同时只能有一个句柄可以向设备发送I/O请求,在CreateFile函数的dwShareMode参数中可以指明是否独占设备。我们并不需要独占设备,这样这里使用FALSE
◎ DeviceObject--指向一个变量,如果函数调用成功的话,变量中将返回指向新创建的设备对象(DEVICE_OBJECT结构)的指针。
接下来,如果对IoCreateSymbolicLink的调用失败的话,我们需要从系统中将设备删除,所以要将IoCreateDevice函数返回的设备对象指针保存起来,以便在删除设备的时候使用。
设备对象指针在卸载驱动的DriverUnload过程中也要用到,但是那时在驱动对象中也可以得到设备对象指针,所以没有必要专门定义一个全局变量将设备对象指针保留到那个时候。
如果设备被成功地创建,为了使它能被Windows子系统"看见",我们还需要调用IoCreateSymbolicLink函数创建符号链接,接下来就是指定分派过程:
pDriverObject^.MajorFunction[IRP_MJ_CREATE] := @DispatchCreateClose;
pDriverObject^.MajorFunction[IRP_MJ_CLOSE] := @DispatchCreateClose;
pDriverObject^.MajorFunction[IRP_MJ_DEVICE_CONTROL] := @DispatchControl;

每个驱动程序都包括一个过程入口指针数组,用来指明不同的I/O请求被分派到那个函数来处理。每个驱动程序必须至少设置一个过程入口,用来处理IRP_MJ_XXX类型的请求。不同的驱动程序可以设置多个不同的过程入口,用来处理不同的IRP_MJ_XXX请求代码。例如,如果你需要得到"系统将要关闭"的通知的话,就必须"申明"处理该请求的分派过程,也就是在驱动对象的MajorFunction表中的IRP_MJ_SHUTDOWN一栏中填入该分派过程的地址。如果不需要处理某个请求,那么什么都不用做,因为I/O管理器在调用DriverEntry前默认将MajorFunction表中的每一项都填成了系统内部的IopInvalidDeviceRequest过程的地址,该过程会返回一个错误代码。
所以,你的责任就是要为每个你想要响应的I/O代码提供分派过程。
在驱动中我们必须至少处理3种I/O请求包,每个内核模式的驱动程序必须支持功能码IRP_MJ_CREATE,这样才能响应Win32的CreateFile函数调用,没有这个分派过程的话,Win32应用程序将无法获取设备的句柄;同样,IRP_MJ_CLOSE也是必须被支持的,否则就无法响应Win32的CloseHandle调用;最后,IRP_MJ_DEVICE_CONTROL允许用户模式程序通过Win32的DeviceIoControl调用来和驱动程序通讯,所以也必须被支持。
下面是这些功能码的说明:
◎ IRP_MJ_CREATE--用户模式代码调用CreateFile函数来获取目标设备对象的文件对象句柄时,I/O管理器发送此代码
◎ IRP_MJ_DEVICE_CONTROL--用户模式代码调用DeviceIoControl函数时,I/O管理器发送此代码
◎ IRP_MJ_CLOSE--用户模式代码调用CloseHandle函数来关闭目标设备对象的文件对象句柄时,I/O管理器发送此代码
pDriverObject^.DriverUnload := @DriverUnload;

DriverUnload过程的意图在于清理DriverEntry过程申请的一些资源,如果驱动需要被动态卸载的话,我们就必须提供卸载进程的分派过程,当用户模式代码使用SERVICE_CONTROL_STOP参数调用ControlService函数时,该分派过程就会被调用。
如果调用IoCreateSymbolicLink失败,那么我们必须释放前面申请的一些资源,这里我们要删除前面用IoCreateDevice创建的设备对象,这可以通过调用IoDeleteDevice函数来完成。如果你还申请了别的一些资源的话,在这里也应该全部将它归还给系统。
请不要忘了,你必须随时留意你申请的内存和其他一些系统资源,在不需要再使用的话,要将它们释放掉。因为你现在是在内核模式下运行,这些清理工作必须自己完成,没人会帮你做这些事情。
最后,我们向系统返回状态代码,如果代码是STATUS_SUCCESS的话,驱动程序将保留在内存中,接下来I/O管理器会将对应的IRP请求发送给它;如果返回的是其他数值的话,系统会将驱动程序从内存中清除。
DriverEntry成功返回后,系统中多了三个新的对象,驱动"\Driver\VirtToPhys",设备"\Device\devVirtToPhys"以及到设备的符号连接"\??\slVirtToPhys"。
驱动对象描述了系统中存在的独立的驱动程序,I/O管理器通过驱动对象获取每个驱动中不同的分派过程的入口地址。
设备对象描述了系统中的一个设备,包括设备的各种特征。通过设备对象,I/O管理器得到管理这个设备的驱动对象的指针。
文件对象是设备对象在用户模式上的表现,通过文件对象,I/O管理器得到设备对象的指针。
符号连接对用户模式是可见的,它被对象管理器所使用。
下图显示了各对象之间的相互联系,它能帮你更彻底地理解后面的内容。
screen.width-333)this.width=screen.width-333" dypop="按此在新窗口浏览图片">  
    I/O管理器调用分派过程来响应用户模式或者内核模式的请求,在单层或者多层中的最高层的驱动中,分派过程保证是在发起I/O请求的线程上下文中执行的,就像DriverEntry过程一样,分派过程也是在IRQL = PASSIVE_LEVEL下执行的,这意味着它们可以存取分页的系统资源。
所有的分派过程的申明如下:
Function DispatchRoutine(pDeviceObject: PDEVICE_OBJECT, pIrp:PIRP): NTSTATUS; stdcall;

  参数描述如下:
◎ pDeviceObject--指向设备对象(DEVICE_OBJECT结构),如果同一个驱动程序负责多个设备的话,从这个参数就能分辨出是哪个设备发送过来的IRP
◎ pIrp--指向描述I/O请求的IRP结构

I/O管理器创建一个IRP结构,用来描述I/O请求,并把它的指针通过pIrp参数传递给设备驱动程序,具体怎样处理就是设备驱动程序的事情了。
这种统一格式的接口的好处在于:I/O管理器可以用同样的方法调用任何的分派过程,而不需要知道驱动程序内部的细节知识(注:反过来想一下,如果不同分派过程的调用格式不同,那么I/O管理器必须知道所有的过程的调用格式和参数定义)。
为什么不同类型的IRP可以用同一个分派过程来处理呢?这是因为在我们这个简单的驱动程序中,唯一要在IRP_MJ_CREATE和IRP_MJ_CLOSE中要做的事情就是将IRP标记为已处理。
如果两者的处理方法不同的话,你还是应该创建独立的DispatchCreate的DispatchClose过程。
前面已经说过,处理IRP_MJ_CREATE是为了响应CreateFile的调用,如果不处理这个代码的话,Win32应用程序将无法获取设备句柄;同样处理IRP_MJ_CLOSE代码是为了响应对CloseHandle的调用。
p_Irp^.IoStatus.Status := STATUS_SUCCESS;
p_Irp^.IoStatus.Information := 0;

我们填写I/O状态块来表示IRP的处理结果。
I/O状态块的Information字段被设置为0,表示设备句柄可以被打开。该字段对关闭的请求来说没有什么含义,但对其他的请求可能有不同的含义。
返回值决定了CreateFile或CloseHandle的调用是否成功返回,所以我们要在这里填写STATUS_SUCCESS。
IofCompleteRequest(p_Irp, IO_NO_INCREMENT);
result := STATUS_SUCCESS;

现在必须调用IoCompleteRequest函数来表示驱动程序已经完成了IRP的处理,并将IRP返回给I/O管理器;然后返回STATUS_SUCCESS表示设备已经可以接收另一个I/O请求的处理了。
IoCompleteRequest的第一个参数告诉I/O管理器哪个IRP已经被处理完毕,第二个参数返回一个系统定义的表示实时优先级的常数,这是驱动程序为了补偿其他线程(在驱动的执行中)进行等待而给予的瞬间的优先级提高,例如对于音频设备,DDK建议使用IO_SOUND_INCREMENT值(等于8)。
例子程序中使用IO_NO_INCREMENT(等于0),也就是说当前线程的优先级保持不变。
I/O管理器提供了3种缓冲管理方式:buffered方式、direct方式和neither方式。三种方式在这里我就不详细介绍了,这方面的资料很多^_^。
    程序的其他部分都很简单,大家一看就能明白,这里还有两个地方要说明一下:
    一是关于虚拟地址转换成物理地址的问题,程序中我们使用了ntoskrnl.exe中的MmGetPhysicalAddress,其函数原型在C语言中定义如下:
NTKERNELAPI PHYSICAL_ADDRESS MmGetPhysicalAddress (IN PVOID BaseAddress);

    这里PHYSICAL_ADDRESS是LARGE_INTEGER的别名,LARGE_INTEGER是一个结构,转换成Pascal记录后定义如下:
  
TLargeInteger=record
       LowPart:Cardinal;
       HighPart:Integer;
   end;

    当执行liPhysicalAddress := MmGetPhysicalAddress(PVOID(pSystemBuffer^))这条语句时,由于返回的是记录,delphi会把返回值liPhysicalAddress当成MmGetPhysicalAddress的第二个参数压入堆栈,程序编译后会生成如下的代码:
    
move ax, [pSystemBuffer] 
      push eax
      lea  eax,liPhysicalAddress
      push eax
      call  MmGetPhysicalAddress

    这显然是不对的,这样不但的不到想要的结果,还会破坏堆栈,结果就是可爱的蓝屏。此处我写了个Delphi包装函数解决这个问题,就像下面这样:
function krnlMmGetPhysicalAddress(BaseAddress: PVOID): PHYSICAL_ADDRESS; external NtKernel name '_MmGetPhysicalAddress';{引入MmGetPhysicalAddress}

function MmGetPhysicalAddress(BaseAddress: PVOID): PHYSICAL_ADDRESS;
var
  liPa :PHYSICAL_ADDRESS;
begin
  liPa.LowPart := 0;
  liPa.HighPart := 0;
  asm
    pushad {保存寄存器,内核程序安全第一,有把握可以不要^_^}
    push BaseAddress
    call krnlMmGetPhysicalAddress
    mov liPa.LowPart, eax
    popad
  end;
  result := liPa;
end;

    实际上MmGetPhysicalAddress也只返回了一个32位整数到EAX中,可不知道为什么microsoft要把返回值定义成LARGE_INTEGER,可能是为了今后扩展吧。
   二是关于fastcall调用,内核APIs有一部分是fastcall调用约定的,Delphi也支持fastcall调用约定,就是register call,但是register call和microsoft的fastcall是有区别的,microsoft的fastcall使用两个寄存器ECX和EDX保存前两个参数,其余的按从右到左的顺序压栈传送;而register call使用三个寄存器EAX、EDX、ECX传送头三个参数,其余按从左到右的顺序压栈传送。所以对使用fastcall调用约定的APIs也必须用delphi做个包装函数,举例如下:
procedure krnlObfDereferenceObject(_Object: PVOID); register; external NtKernel name '_ObfDereferenceObject';
procedure ObfDereferenceObject(_Object: PVOID); assembler;
asm
  mov ecx, _Object
  call krnlObfDereferenceObject
end;
2009-08-06 17:41:00 xxagri 阅读数 296
  • DelphiXE10零基础实战快速入门

    通过课堂上一个一个的实战例子,演示DelphiXE10的用法,最后通过一个完整的通讯录程序,让学员初步掌握软件开发的全部流程 让零基础学员快速入门Delphi编程,快速掌握Delphi的使用方法,并能够制作出自己的软件来。为同学们下一步的提高打下坚实的基础。

    1044 人正在学习 去看看 陈城

Delphi驱动开发研究第九篇--文件与目录 
 
[日期:2009-03-07] 作者:mickeylan 来源:Delphi驱动 点击:412
 
提供对文件的读写功能是操作系统的一项重要任务。我们来看一下NT家族的操作系统都为我们提供了那些功能。
9.1 核心句柄表
在开始讨论本文的主题之前,我们先来讨论一个重要的问题,我们之前并未对其给予应有的注意。为了取得对象的句柄需要填充OBJECT_ATTRIBUTES结构体——我们已经做过很多遍了,其样子如下:
InitializeObjectAttributes(oa, @g_usName,OBJ_CASE_INSENSITIVE,0, nil);

初始化了OBJECT_ATTRIBUTES后,我们调用函数创建或打开这个对象并获得其句柄(handle)。但这个句柄进入的是得到上下文的那个进程的句柄表。因为对于进程句柄表是其特有的,所以使用这个句柄就只能在进程自己的上下文中。例如,若是试图在其它进程的上下文中打开这个句柄的话,好的情况下会操作失败,而运气不好的话,要是在这个进程句柄表中有取值相同的句柄——要知道句柄只是一个32位的数(准确地讲是一个位的结构体)——就可能关闭其它对象。甚至如果获得的句柄是驱动句柄,但是是在用户进程中,句柄就会进入这个进程的句柄表并有可能有意或无意地在用户模式下使用对象。不希望的事情却总是发生,这样的情况常常出现。正是因此,内核组件和驱动程序有其特殊性,它们不喜欢使用句柄,而是使用reference to object,这样比较好,只需简单地使用指向内存中对象结构体的指针。为了统计对对象的引用,在对象的首部保存着一个引用计数(reference count)。如果需要像本例和上例中的那样访问对象,就要设计一个循环,在不同的上下文中对其进行访问,让系统将句柄放入核心句柄表中(kernel handle table)。
从Windows 2000开始,在系统中有了专门的核心句柄表。在这个表中的句柄只能内核模式下的任意进程上下文中访问,与进程特有的句柄不同。甚至于,比如说如果在System进程的上下文、在DriverEntry函数中获得句柄,则就不能在用户进程上下文中使用对象。System进程实现了自己私有的句柄表,其与核心句柄表不同。
对于在核心句柄表中的句柄,需要在调用InitializeObjectAttributes宏时显式地设置OBJ_KERNEL_HANDLE标志,形式如下:
InitializeObjectAttributes(oa, @g_usName, OBJ_KERNEL_HANDLE, 0,nil);

9.2 FileWorks驱动程序源代码
就像上一例中的驱动程序,本例的驱动程序的代码也是由几个独立的函数构成的:CreateDirectory、CreateFile、WriteFile、MarkAsReadOnly、ReadFile、UnmarkAsReadOnly、AppendFile、TruncateFile、DeleteFile、DeleteDirectory和EnumerateFiles。几乎所有的函数都是独立工作的。
unit FileWorks;

interface

uses
  nt_status, ntoskrnl, ntifs, native, winioctl, fcall, macros;

function _DriverEntry(pDriverObject:PDRIVER_OBJECT;
                      pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall;

implementation

var
  g_usDirName: UNICODE_STRING;
  g_usFileName: UNICODE_STRING;

procedure CreateDirectory;
var
  oa: OBJECT_ATTRIBUTES;
  iosb: IO_STATUS_BLOCK;
  hDirectory: THANDLE;
  RtnCode: NTSTATUS;
begin
  { 还记得吧, 传递给DbgPrint函数的用于格式化Unicode的代码(%C, %S, %lc, %ls, %wc, %ws, %wZ)只能在
    IRQL = PASSIVE_LEVEL下调用! }
  DbgPrint(#13#10'FileWorks: Creating %ws directory'#13#10, g_usDirName.Buffer);

  InitializeObjectAttributes(oa, @g_usDirName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwCreateFile(@hDirectory, SYNCHRONIZE, @oa, @iosb, nil,
                          FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF,
                          FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT,
                          nil, 0);
  if RtnCode = STATUS_SUCCESS then
  begin
    if iosb.Information = FILE_CREATED then
    begin
      DbgPrint('FileWorks: Directory created'#13#10);
    end else if iosb.Information = FILE_OPENED then
    begin
      DbgPrint('FileWorks: Directory exists and was opened'#13#10);
    end;
    ZwClose(hDirectory);
  end else
  begin
    DbgPrint('FileWorks: Can''t create directory. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure CreateFile;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  RtnCode: NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Creating %ws file'#13#10, g_usFileName.Buffer);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);

  RtnCode := ZwCreateFile(@hFile, SYNCHRONIZE, @oa, @iosb, nil,
                          FILE_ATTRIBUTE_NORMAL, 0,
                          FILE_CREATE,
                          FILE_SYNCHRONOUS_IO_NONALERT, nil, 0);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File created'#13#10);
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t create file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure WriteFile;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  RtnCode: NTSTATUS;
  g_szData: PAnsiChar;
begin
  DbgPrint(#13#10'FileWorks: Opening file for writing'#13#10);

  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwCreateFile(@hFile, FILE_WRITE_DATA + SYNCHRONIZE,
                          @oa, @iosb, nil, 0, FILE_SHARE_READ,
                          FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                          nil, 0);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    g_szData := 'Data can be written to an open file';
    //RtlInitUnicodeString(g_szData, 'Data can be written to an open file');
    RtnCode := ZwWriteFile(hFile, 0, nil, nil, @iosb, g_szData,
                           strlen(g_szData), nil, nil);
    if RtnCode = STATUS_SUCCESS then
    begin
      DbgPrint('FileWorks: File was written'#13#10);
    end else
    begin
      DbgPrint('FileWorks: Can''t write to the file. Status: %08X/n', RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure MarkAsReadOnly;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THandle;
  fbi:FILE_BASIC_INFORMATION;
  RtnCode:NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Opening file for changing attributes'#13#10);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwCreateFile(@hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SYNCHRONIZE,
                          @oa, @iosb, nil, 0, FILE_SHARE_READ,
                          FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                          nil, 0);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    RtnCode := ZwQueryInformationFile(hFile, @iosb, @fbi,
                                      sizeof(fbi),
                                      FileBasicInformation);
    if RtnCode = STATUS_SUCCESS then
    begin
      DbgPrint('FileWorks: File attributes were: %08X'#13#10, fbi.FileAttributes);
      fbi.FileAttributes := fbi.FileAttributes or FILE_ATTRIBUTE_READONLY;
      RtnCode := ZwSetInformationFile(hFile, @iosb, @fbi,
                                      sizeof(fbi), FileBasicInformation);
      if RtnCode = STATUS_SUCCESS then
      begin
        DbgPrint('FileWorks: Now file marked as read-only'#13#10);
      end else
      begin
        DbgPrint('FileWorks: Can''t change file attributes. Status: %08X'#13#10, RtnCode);
      end;
    end else
    begin
      DbgPrint('FileWorks: Can''t query file attributes. Status: %08X'#13#10, RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X/n', RtnCode);
  end;
end;

procedure ReadFile;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  p:PVOID;
  cb:DWORD;
  fsi:FILE_STANDARD_INFORMATION;
  RtnCode: NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Opening file for reading'#13#10);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwOpenFile(@hFile, FILE_READ_DATA + SYNCHRONIZE,
                        @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE,
                        FILE_SYNCHRONOUS_IO_NONALERT);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    RtnCode := ZwQueryInformationFile(hFile, @iosb, @fsi,
                                      sizeof(fsi),
                                      FileStandardInformation);
    if RtnCode = STATUS_SUCCESS then
    begin
      cb := fsi.EndOfFile.LowPart + 1;
      p := ExAllocatePool(PagedPool, cb);
      if p <> nil then
      begin
        memset(p, 0, cb);
        RtnCode := ZwReadFile(hFile, 0, nil, nil, @iosb, p, cb, nil, nil);
        if RtnCode = STATUS_SUCCESS then
        begin
          DbgPrint('FileWorks: File content: /=%s/='#13#10, p);
        end else
        begin
          DbgPrint('FileWorks: Can''t read from the file. Status: %08X'#13#10, RtnCode);
        end;
        ExFreePool(p);
      end else
      begin
        DbgPrint('FileWorks: Can''t allocate memory. Status: %08X'#13#10, RtnCode);
      end;
    end else
    begin
      DbgPrint('FileWorks: Can''t query file size. Status: %08X'#13#10, RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure UnmarkAsReadOnly;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  fbi:FILE_BASIC_INFORMATION;
  RtnCode:NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Opening file for changing attributes'#13#10);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwCreateFile(@hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SYNCHRONIZE,
                          @oa, @iosb, nil, 0, FILE_SHARE_READ,
                          FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                          nil, 0);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    RtnCode := ZwQueryInformationFile(hFile, @iosb, @fbi,
                                      sizeof(fbi), FileBasicInformation);
    if RtnCode = STATUS_SUCCESS then
    begin
      DbgPrint('FileWorks: File attributes were: %08X'#13#10, fbi.FileAttributes);
      fbi.FileAttributes := fbi.FileAttributes and (not FILE_ATTRIBUTE_READONLY);
      RtnCode := ZwSetInformationFile(hFile, @iosb, @fbi, sizeof(fbi), FileBasicInformation);
      if RtnCode = STATUS_SUCCESS then
      begin
        DbgPrint('FileWorks: Now file can be written or deleted'#13#10);
      end else
      begin
        DbgPrint('FileWorks: Can''t change file attributes. Status: %08X'#13#10, RtnCode);
      end;
    end else
    begin
      DbgPrint('FileWorks: Can''t query file attributes. Status: %08X'#13#10, RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure AppendFile;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  g_szDataToAppend:PAnsiChar;
  RtnCode:NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Opening file to append data'#13#10);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwOpenFile(@hFile, FILE_APPEND_DATA + SYNCHRONIZE,
                        @oa, @iosb, FILE_SHARE_READ,
                        FILE_SYNCHRONOUS_IO_NONALERT);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    g_szDataToAppend := ' using ZwWriteFile';
    RtnCode := ZwWriteFile(hFile, 0, nil, nil, @iosb, g_szDataToAppend,
                           strlen(g_szDataToAppend), nil, nil);
    if RtnCode = STATUS_SUCCESS then
    begin
      DbgPrint('FileWorks: Data appended to the file'#13#10);
    end else
    begin
      DbgPrint('FileWorks: Can''t append data to file. Status: %08X'#13#10, RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure TruncateFile;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  fsi:FILE_STANDARD_INFORMATION;
  feofi:FILE_END_OF_FILE_INFORMATION;
  RtnCode:NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Opening file to truncate'#13#10);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwOpenFile(@hFile, FILE_WRITE_DATA + SYNCHRONIZE,
                        @oa, @iosb, FILE_SHARE_READ,
                        FILE_SYNCHRONOUS_IO_NONALERT);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    RtnCode := ZwQueryInformationFile(hFile, @iosb, @fsi,
                                      sizeof(fsi),
                                      FileStandardInformation);
    if RtnCode = STATUS_SUCCESS then
    begin
      DbgPrint('FileWorks: EOF was: %08X'#13#10, fsi.EndOfFile.LowPart);
      feofi.EndOfFile.HighPart := 0;
      feofi.EndOfFile.LowPart := (fsi.EndOfFile.LowPart) shr 1;
      RtnCode := ZwSetInformationFile(hFile, @iosb, @feofi,
                                      sizeof(feofi),
                                      FileEndOfFileInformation);
      if RtnCode = STATUS_SUCCESS then
      begin
        DbgPrint('FileWorks: File truncated to its half size'#13#10);
      end else
      begin
        DbgPrint('FileWorks: Can''t truncate file. Status: %08X'#13#10, RtnCode);
      end;
    end else
    begin
      DbgPrint('FileWorks: Can''t query file info. Status: %08X'#13#10, RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure DeleteFile;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  fdi:FILE_DISPOSITION_INFORMATION;
  RtnCode:NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Opening file for deletion'#13#10);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwCreateFile(@hFile, _DELETE + SYNCHRONIZE,
                          @oa, @iosb, nil, 0, FILE_SHARE_DELETE,
                          FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                          nil, 0);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    fdi.DeleteFile := True;
    RtnCode := ZwSetInformationFile(hFile, @iosb, @fdi,
                                    sizeof(fdi),
                                    FileDispositionInformation);
    if RtnCode = STATUS_SUCCESS then
    begin
      DbgPrint('FileWorks: File has been marked for deletion'#13#10);
      DbgPrint('FileWorks: It should be deleted when the last open handle is closed'#13#10);
    end else
    begin
      DbgPrint('FileWorks: Can''t mark file for deletion. Status: %08X'#13#10, RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure DeleteDirectory;
var
  oa:OBJECT_ATTRIBUTES;
  RtnCode:NTSTATUS;
begin
  InitializeObjectAttributes(oa, @g_usDirName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwDeleteFile(@oa);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint(#13#10'FileWorks: Directory should be deleted'#13#10);
  end else
  begin
    DbgPrint(#13#10'FileWorks: Can''t delete directory. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure EnumerateFiles;
var
  oa:OBJECT_ATTRIBUTES;
  hSystemRootDirectory:THANDLE;
  hDriversDirectory:THANDLE;
  _as:ANSI_STRING;
  us:UNICODE_STRING;
  iosb:IO_STATUS_BLOCK;
  tf:TIME_FIELDS;
  cb:DWORD;
  pfdi:PFILE_DIRECTORY_INFORMATION;
  RtnCode:NTSTATUS;
  g_usTemp:UNICODE_STRING;
begin
  DbgPrint(#13#10'FileWorks: Opening directory to enumerate files'#13#10);
  RtlInitUnicodeString(g_usTemp, '/SystemRoot');
  InitializeObjectAttributes(oa, @g_usTemp,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwOpenFile(@hSystemRootDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE,
                        @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE,
                        FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT);
  if RtnCode = STATUS_SUCCESS then
  begin
    RtlInitUnicodeString(g_usTemp, 'system32/drivers');
    InitializeObjectAttributes(oa, @g_usTemp,
                               OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                               hSystemRootDirectory, nil);
    RtnCode := ZwOpenFile(@hDriversDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE,
                          @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE,
                          FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT);
    if RtnCode = STATUS_SUCCESS then
    begin
      cb := sizeof(FILE_DIRECTORY_INFORMATION) + 256;
      pfdi := ExAllocatePool(PagedPool, cb);
      if pfdi <> nil then
      begin
        RtlInitUnicodeString(g_usTemp, 'c*');
        DbgPrint(#13#10'FileWorks: ---------- Starting enumerate files ----------'#13#10);
        RtnCode := ZwQueryDirectoryFile(hDriversDirectory, 0, nil, nil, @iosb,
                                        pfdi, cb, FileDirectoryInformation,
                                        true, @g_usTemp, true);
        while RtnCode <> STATUS_NO_MORE_FILES do
        begin
          if RtnCode = STATUS_SUCCESS then
          begin
            us.Length := pfdi^.FileNameLength;
            us.MaximumLength := pfdi^.FileNameLength;
            us.Buffer := PWideChar(@pfdi^.FileName);
            if RtlUnicodeStringToAnsiString(@_as, @us, true) = STATUS_SUCCESS then
            begin
              RtlTimeToTimeFields(@pfdi^.CreationTime, @tf);
              DbgPrint(' %s size=%d created on %d.%02d.%04d'#13#10,
                       _as.Buffer, pfdi^.EndOfFile.LowPart,
                       BYTE(tf.Day), BYTE(tf.Month), WORD(tf.Year));
              RtlFreeAnsiString(@_as);
            end;
          end;
          RtnCode := ZwQueryDirectoryFile(hDriversDirectory, 0, nil, nil, @iosb,
                                          pfdi, cb, FileDirectoryInformation,
                                          true, nil, false);
        end; {End While}
        DbgPrint('FileWorks: ------------------------------------------------'#13#10);
        ExFreePool(pfdi);
      end;
      ZwClose(hDriversDirectory);
    end else
    begin
      DbgPrint('FileWorks: Can''t open drivers directory. Status: %08X', RtnCode);
    end;
    ZwClose(hSystemRootDirectory);
  end else
  begin
    DbgPrint('FileWorks: Can''t open system root directory. Status: %08X'#13#10, RtnCode);
  end;
end;

function _DriverEntry(pDriverObject:PDRIVER_OBJECT;
                      pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall;
begin
  DbgPrint(#13#10'FileWorks: Entering DriverEntry'#13#10);
  RtlInitUnicodeString(g_usFileName, '/??/c:/FileWorks/test.txt');
  RtlInitUnicodeString(g_usDirName, '/??/c:/FileWorks');
  CreateDirectory;
  CreateFile;
  WriteFile;
  MarkAsReadOnly;
  ReadFile;
  UnmarkAsReadOnly;
  AppendFile;
  ReadFile;
  TruncateFile;
  ReadFile;
  DeleteFile;
  DeleteDirectory;
  EnumerateFiles;
  DbgPrint(#13#10'FileWorks: Leaving DriverEntry'#13#10);
  result := STATUS_DEVICE_CONFIGURATION_ERROR;
end;

end.


9.3 创建目录与文件
InitializeObjectAttributes(oa, @g_usDirName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil);
填充OBJECT_ATTRIBUTES结构体,不要忘了OBJ_KERNEL_HANDLE标志。我想要强调的是,在本例中这个操作并不是必需的,因为我们不会在其它进程中使用这些句柄。但是因为FileWorks驱动程序的函数可以很容易的移植到使用任意进程上下文的程序中,所以我决定设置这个标志。如果在您的程序中有驱动和用户进程的共用的句柄,则不应设置此标志。如果只在自己一个进程上下文中使用对象,也不必设置OBJ_KERNEL_HANDLE标志。
创建目录和文件用的都是ZwCreateFile函数。从系统角度看,目录也是文件,所以创建目录的函数与创建文件的函数没有本质上的差别。所以,创建目录与创建文件使用同一个函数,只是创建目录要用FILE_DIRECTORY_FILE标志。
RtnCode := ZwCreateFile(@hDirectory, SYNCHRONIZE, @oa, @iosb, nil,FILE_ATTRIBUTE_NORMAL, 0,
                                    FILE_OPEN_IF,FILE_DIRECTORY_FILE+FILE_SYNCHRONOUS_IO_NONALERT,
                                    nil, 0);

ZwCreateFile函数有相当多的参数,所以我在下面给出了它的原型,而且不得不使用了C语言的语义,以显示出输入(IN)、输出(OUT)和可选的参数。
NTSTATUS
ZwCreateFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL,
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG CreateDisposition,
IN ULONG CreateOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength
);

函数成功完成后,参数FileHandle取得所建立的目录的句柄。DesiredAccess定义了对目录的三种访问请求。我们用标志SYNCHRONIZE来定义,这个值的含义在后面会清楚。ObjectAttributes想必您已经知道了。函数成功完成后,参数IoStatusBlock就是指向IO_STATUS_BLOCK结构体的指针,从中可以取出相关的的信息。参数FileAttributes定义了所创建目录的属性(只读、隐藏等)。我们使用FILE_ATTRIBUTE_NORMAL,本例中不需要为目录指定什么特别的属性。可选参数AllocationSize为0定义了所创建文件的大小为0。对于目录来说这是很自然的。参数ShareAccess定义为什么样的值,就要以什么样的权限访问目录。在本例中,我们不允许其它程序访问目录,故将此参数设为0。参数CreateDisposition定义了该目录已经存在,或者相反,文件不存在时系统的行为。我们使用标志FILE_OPEN_IF,这个标志表示如果目录已经存在,就将其打开。我们向参数CreateOptions传递的是标志FILE_DIRECTORY_FILE和FILE_SYNCHRONOUS_IO_NONALERT的组合。第一个表示要建立的是目录而非文件,无需特别解释。FILE_SYNCHRONOUS_IO_NONALERT定义了()对文件所有的操作都要是同步的,例如,调用ZwReadFile后在没有实际读取完文件数据时函数不会返回。在I/O管理器中为文件维护了一个当前文件位置上下文(file position context)。如果设置了标志FILE_SYNCHRONOUS_IO_NONALERT,则在参数DesiredAccess里应该定义为标志SYNCHRONIZE。最后两个参数不用于驱动程序。
if RtnCode = STATUS_SUCCESS then
begin
    if iosb.Information = FILE_CREATED then
    begin
      ……     
    end else if iosb.Information = FILE_OPENED then
    begin
      ……     
end;

正如我所讲的,在IO_STATUS_BLOCK结构体中会有额外的信息。
还记得我们是如何处理I/O请求的(见前面的章节)。例如,在驱动程序SharingMemory中对IRP_MJ_CREATE和IRP_MJ_CLOSE的处理如下:
p_Irp^.IoStatus.Status := STATUS_SUCCESS;
p_Irp^.IoStatus.Information := 0;
IofCompleteRequest(p_Irp, IO_NO_INCREMENT);

本例的驱动程序也大致相同,即将完成创建文件的请求。只有放入IO_STATUS_BLOCK结构体的域中的值会依赖于请求的类型。
因为我们定义了FILE_OPEN_IF标志,通过iosb.Information的值,我们可以知道是该建立新的目录还是因该目录已经存在而将其打开。
ZwClose(hDirectory);

我再重复一遍,在内核模式下一定要显式地关闭所有打开的句柄。
RtnCode := ZwCreateFile(@hFile, SYNCHRONIZE, @oa, @iosb, nil,
                     FILE_ATTRIBUTE_NORMAL, 0,
                     FILE_CREATE,
                     FILE_SYNCHRONOUS_IO_NONALERT, nil, 0);

如您所见,文件的创建实际上是相同的,只是要去掉FILE_DIRECTORY_FILE标志。而FILE_CREATE标志我将其用于多种情况。它表示只可以创建文件。如果文件已经存在,则对ZwCreateFile会以失败结束。

9.4 文件对象
每一个打开的文件句柄都对应着一个文件对象(file object),在内核内存区中有FILE_OBJECT结构体。
TFileObject=packed record
    wType:Word;
    Size:Word;
    DeviceObject:PDeviceObject;
    DoNotUser1:Pointer;
    FsContext:Pointer;
    FsContext2:Pointer;
    SectionObjectPointer:Pointer;
    PrivateCacheMap:Pointer;
    FinalStatus:NTSTATUS;
    RelatedFileObject:PFileObject;
    LockOperation:Boolean;
    DeletePending:Boolean;
    ReadAccess:Boolean;
    WriteAccess:Boolean;
    DeleteAccess:Boolean;
    SharedRead:Boolean;
    SharedWrite:Boolean;
    SharedDelete:Boolean;
    Flags:Cardinal;
    FileName:TUnicodeString;
    CurrentByteOffset:TLargeInteger;
    Waiters:Cardinal;
    Busy:Cardinal;
    LastLock:Pointer;
    Lock:TKEvent;
    Event:TKEvent;
   CompletionContext:Pointer;
end;

例如,我们可以两次打开同一个文件,但是两次的访问请求却不相同:第一次读(FILE_READ_DATA)而第二次写(FILE_WRITE_DATA)。结果内核会建立两个FILE_OBJECT结构体,每一个都对应于自己相应的文件句柄。但是两个句柄和相对应的两个FILE_OBJECT结构体都对应着同一个磁盘文件。第一个FILE_OBJECT结构体将设置ReadAccess域,第二个则设置WriteAccess域。例如,试图写入第一个句柄指向的文件时,系统会发现域WriteAccess = FALSE并结束请求。
DeviceObject域将包含指向设备对象/Device/HarddiskVolume1的指针,因为这个设备是对/??/c:符号链接,我们创建文件时在文件名中使用了/??/c:。
在读完本文后您可能会进行文件操作的实验并会发现系统是如何填充并管理FILE_OBJECT结构体的。如果在内存中定位它时发生问题,可以使用ObReferenceObjectByHandle函数,形式大致如下:
var
pFileObject:PFILE_OBJECT;
. . .
begin
RtnCode := ObReferenceObjectByHandle(hFile, FILE_READ_DATA, nil, KernelMode, @pFileObject,nil);
  if RtnCode = STATUS_SUCCESS then
begin
    {pFileObject指向对应于hFile的FILE_OBJECT}
    ObfDereferenceObject(pFileObject);
end;
end;

ObReferenceObjectByHandle向参数pFileObject中返回指向对应于该文件句柄的FILE_OBJECT结构体的指针。这时,引用计数会增一。之后一定要调用ObfDereferenceObject来使其复原。
9.5 写入文件
到这里我们已经有了大小为0目录和文件。该向文件中写点东西了。
RtnCode := ZwCreateFile(@hFile, FILE_WRITE_DATA + SYNCHRONIZE,
                     @oa, @iosb, nil, 0, FILE_SHARE_READ,
                     FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                     nil, 0);

借助于ZwCreateFile不止可以创建文件,还可以打开已有的文件(恰好,不只是文件,其它某些对象也是这样)。为此需要指定标志FILE_OPEN。
为了写入文件,需要相应的访问权——使用FILE_WRITE_DATA。
在单元文件中定义了几个常量以用于对文件的一般访问。例如,FILE_GENERIC_WRITE不仅允许向文件中写入数据,还可以更改其属性并添加数据。
FILE_GENERIC_WRITE = (STANDARD_RIGHTS_WRITE or
                                    FILE_WRITE_DATA or
                                    FILE_WRITE_ATTRIBUTES or
                                    FILE_WRITE_EA or
                                    FILE_APPEND_DATA or
                                    SYNCHRONIZE);

显然,当我们写文件,其它的程序不应向该文件写入。使用FILE_SHARE_READ标志,则没有程序能写入或是删除文件,而只能读取文件。
if RtnCode = STATUS_SUCCESS then
begin
    DbgPrint('FileWorks: File openeded'#13#10);
    g_szData := 'Data can be written to an open file';
    RtnCode := ZwWriteFile(hFile, 0, nil, nil, @iosb, g_szData,
                           strlen(g_szData), nil, nil);

函数ZwWriteFile见名则知意。
NTSTATUS
ZwWriteFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID Buffer,
IN ULONG Length,
IN PLARGE_INTEGER ByteOffset OPTIONAL,
IN PULONG Key OPTIONAL
);

ZwWriteFile的参数至少要有文件句柄、IO_STATUS_BLOCK结构体指针——在该结构体中将放置额外的信息(其中包括向文件写入的字节数)、指向需要写入文件的数据的指针和其大小。
9.6 修改文件属性
我们需要防止我们的文件被删除。
RtnCode := ZwCreateFile(@hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES +  SNCHRONIZE, @oa, @iosb, nil, 0, FILE_SHARE_READ,
                          FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                          nil, 0);

标志FILE_READ_ATTRIBUTES和FILE_WRITE_ATTRIBUTES用于取得和更改相应的文件属性。
if RtnCode = STATUS_SUCCESS then
begin
  DbgPrint('FileWorks: File openeded'#13#10);
  RtnCode := ZwQueryInformationFile(hFile, @iosb, @fbi,
                                sizeof(fbi),
                                FileBasicInformation);
  if RtnCode = STATUS_SUCCESS then
  begin

我们需要建立只读属性,但由于文件有其它的属性需要保留,故有:
fbi.FileAttributes := fbi.FileAttributes or FILE_ATTRIBUTE_READONLY;
RtnCode := ZwSetInformationFile(hFile, @iosb, @fbi,
                            sizeof(fbi), FileBasicInformation);

要向文件属性中添加属性,我们必须要用标志。当需要根改文件时,我们用UnmarkAsReadOnly函数去掉这个属性,有:
fbi.FileAttributes := fbi.FileAttributes and (not FILE_ATTRIBUTE_READONLY);

9.7 读取文件
现在,为了各种操作,我们用ZwOpenFile函数打开文件来从中读取。所需要的参数与ZwCreateFile的类似。
NTSTATUS
ZwOpenFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG ShareAccess,
IN ULONG OpenOptions
);

RtnCode := ZwOpenFile(@hFile, FILE_READ_DATA + SYNCHRONIZE,
                        @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE,
                        FILE_SYNCHRONOUS_IO_NONALERT);

FILE_READ_DATA——在这里我们只从文件中读取数据。标志FILE_SHARE_READ、FILE_SHARE_WRITE和FILE_SHARE_DELETE的组合允许对文件进行完全的访问。
if RtnCode = STATUS_SUCCESS then
begin
  DbgPrint('FileWorks: File openeded'#13#10);
  RtnCode := ZwQueryInformationFile(hFile, @iosb, @fsi,
                                sizeof(fsi),
                                FileStandardInformation);
 if RtnCode = STATUS_SUCCESS then
  begin

我们以准备好读取文件的所有数据,但是用于这个请求的缓冲区的大小依赖于文件的大小。文件的大小可以用信息类FileStandardInformation由函数ZwQueryInformationFile获得,并将其传递给FILE_BASIC_INFORMATION指针。
cb := fsi.EndOfFile.LowPart + 1;

我们来为最后的零添加一个字节。
    p := ExAllocatePool(PagedPool, cb);
      if p <> nil then
      begin
        memset(p, 0, cb);
        RtnCode := ZwReadFile(hFile, 0, nil, nil, @iosb, p, cb, nil, nil);
        if RtnCode = STATUS_SUCCESS then
        begin
          DbgPrint('FileWorks: File content: /=%s/='#13#10, p);
        End;
        ExFreePool(p);
     end;

分配所需的缓冲区,将其清零并传递给ZwReadFile函数。这个函数的原型与ZwWriteFile函数的原型类似,只是缓冲区用处不同。因为在使用之前我们清零了缓冲区,并且其大小比文件的内容多一个字节,所以不会发生将其内容输出到调试信息的问题。
9.8 向文件追加数据
向文件中追加数据有几种方法。可以用标志FILE_WRITE_DATA将其打开,在当前位置建立指向文件末尾的指针,在函数ZwWriteFile的参数ByteOffset中传递偏移量并写入数据。指示文件当前位置的指针保存在FILE_OBJECT.CurrentByteOffset中。我们还可以用标志FILE_APPEND_DATA打开文件,文件当前位置指针会自动指向其末尾。
RtnCode := ZwOpenFile(@hFile, FILE_APPEND_DATA + SYNCHRONIZE,
                    @oa, @iosb, FILE_SHARE_READ,
                    FILE_SYNCHRONOUS_IO_NONALERT);
if RtnCode = STATUS_SUCCESS then
begin
  DbgPrint('FileWorks: File openeded'#13#10);
  g_szDataToAppend := ' using ZwWriteFile';
  RtnCode := ZwWriteFile(hFile, 0, nil, nil, @iosb, g_szDataToAppend,
                      strlen(g_szDataToAppend), nil, nil);

文件的大小会自动增加。
9.9 截短文件
假设我们需要截短文件,去掉不需要的数据。在本例中,我为了简单起见,将文件缩减为原来的一半。
RtnCode := ZwOpenFile(@hFile, FILE_WRITE_DATA + SYNCHRONIZE,
                    @oa, @iosb, FILE_SHARE_READ,
                    FILE_SYNCHRONOUS_IO_NONALERT);

我们用写访问来打开文件。
if RtnCode = STATUS_SUCCESS then
begin
RtnCode := ZwQueryInformationFile(hFile, @iosb, @fsi,
                                 sizeof(fsi),
                                 FileStandardInformation);

我们在FILE_STANDARD_INFORMATION结构体的成员中可以获得文件当前的大小。
    feofi.EndOfFile.HighPart := 0;
      feofi.EndOfFile.LowPart := (fsi.EndOfFile.LowPart) shr 1;
      RtnCode := ZwSetInformationFile(hFile, @iosb, @feofi,
                                  sizeof(feofi),
                                  FileEndOfFileInformation);

使用信息类FileEndOfFileInformation,我们设置新的大小等于当前大小的一半。
9.10 删除文件与目录
小事一桩——将c:恢复为初始的状态。令人奇怪的是,2000 DDK里没有提到存在ZwDeleteFile函数。在XP DDK中说系统终归提供了这个函数,但是是从Windows XP开始的。但事实并非如此。Windows 2000甚至Windows NT4中就有ZwDeleteFile。但对于文件的删除,我们有几种更为复杂的方法,而对目录的删除则借助于ZwDeleteFile。
RtnCode := ZwCreateFile(@hFile, _DELETE + SYNCHRONIZE,
                     @oa, @iosb, nil, 0, FILE_SHARE_DELETE,
                     FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                     nil, 0);

我们打开文件以进行删除。
if RtnCode = STATUS_SUCCESS then
begin
fdi.DeleteFile := True;
  RtnCode := ZwSetInformationFile(hFile, @iosb, @fdi,
                              sizeof(fdi),
                              FileDispositionInformation);

使用信息类FileDispositionInformation并设置文件删除标志。这时在FILE_OBJECT结构体的DeletePending域中的值由FALSE变为TRUE。这表示文件被标记为删除。这样只要存在该文件的一个打开的句柄,文件就不会被删除。
ZwClose(hFile);
现在文件唯一的句柄被关闭,文件被删除。
ZwDeleteFile(@oa);
使用ZwDeleteFile删除目录很简单,我就不多说了。

9.11 列举目录内容
一般说来,有两种方法可以用来定义创建/打开的对象的名字,而文件却特别。访问命名对象可以使用完整的路径,也可以使用符号链接。这里我们只使用绝对路径。例如,路径/??/c:/FileWorks/test.txt就是绝对路径。在本例中,使用InitializeObjectAttributes宏填充InitializeObjectAttributes结构体,其形式如下:
InitializeObjectAttributes(oa, @g_usTemp,
                     OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                     0, nil);

倒数第二个参数RootDirectory为0。InitializeObjectAttributes的参数RootDirectory和该函数填充的OBJECT_ATTRIBUTES的同名域定义了目录容器对象的句柄。
OBJECT_ATTRIBUTES=record
. . .
  RootDirectory:THANDLE;
. . .
end;

如果目录容器对象已经打开,则已经取得了句柄,就可以用相对于目录容器的路径来使用对象。这时目录容器的句柄应该放置在RootDirectory中。例如,如果我们已经打开了目录/??/c:/FileWorks/并将其句柄放入了变量hDirectory中,则对test.txt的文件路径我们可以如下使用:
RtlInitUnicodeString(g_usFileName, '/??/c:/FileWorks/test.txt');
InitializeObjectAttributes(a,@g_usFileName,
OBJ_CASE_INSENSITIVE, hDirectory, nil);

在目录容器下就意味着目录不只是在磁盘上,还在对象管理器的名字空间中。
对于列出系统目录/%SystemRoot%/System32/Drivers/的内容,我们来使用相对路径。
RtlInitUnicodeString(g_usTemp, '/SystemRoot');
InitializeObjectAttributes(oa, @g_usTemp,
                     OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                     0, nil);

我们来填充OBJECT_ATTRIBUTES结构体,使用符号链接/SystemRoot——这个是绝对路径。
RtnCode := ZwOpenFile(@hSystemRootDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE,
                        @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE,
                        FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT);

我们打开目录/%SystemRoot%/。FILE_LIST_DIRECTORY标志用于列举目录内容。
if RtnCode = STATUS_SUCCESS then
begin
  RtlInitUnicodeString(g_usTemp, 'system32/drivers');
  InitializeObjectAttributes(oa, @g_usTemp,
                       OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                       hSystemRootDirectory, nil);

如果目录打开成功,从变量hSystemRootDirectory中我们可以取得其句柄,这个句柄将用作目录容器的句柄。记着OBJECT_ATTRIBUTES结构体使用相对路径“system32/drivers”。
RtnCode := ZwOpenFile(@hDriversDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE,
                          @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE,
                          FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT);
if RtnCode = STATUS_SUCCESS then
begin

我们来用相对路径打开目录/%SystemRoot%/System32/Drivers/。
cb := sizeof(FILE_DIRECTORY_INFORMATION) + 256;
pfdi := ExAllocatePool(PagedPool, cb);
if pfdi <> nil then
begin

分配必要的缓冲区,在缓冲区中应放得下FILE_DIRECTORY_INFORMATION结构体和文件名。
RtlInitUnicodeString(g_usTemp, 'c*');
RtnCode := ZwQueryDirectoryFile(hDriversDirectory, 0, nil, nil, @iosb,
                             pfdi, cb, FileDirectoryInformation,
                             true, @g_usTemp, true);

我们开始列举目录文件,这里用的是信息类FileDirectoryInformation。函数ZwQueryDirectoryFile的原型大概是多余的。
NTSTATUS
ZwQueryDirectoryFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileName OPTIONAL,
IN BOOLEAN RestartScan
);

但这个函数在2000 DDK中也没有被提到。XP DDK说这个函数只从Windows XP开始提供。这同样也不是真的。
我们令参数ReturnSingleEntry为TRUE,这就使ZwQueryDirectoryFile函数只返回一个文件的信息,而且是第一个文件的。参数FileName指向带有搜索条件“c*”的字符串,即只列出文件名以“c”开头的文件。这样能缩短输出的调试信息。在第一次调用ZwQueryDirectoryFile时,我们将参数RestartScan设为TRUE。这就使得ZwQueryDirectoryFile函数开始检查目录的内容。
while RtnCode <> STATUS_NO_MORE_FILES do
begin

循环调用ZwQueryDirectoryFile直到ZwQueryDirectoryFile返回STATUS_NO_MORE_FILES,即目录中所有文件都已经列举。
if RtnCode = STATUS_SUCCESS then
begin

如果突然发现有文件的文件名超过256字节(实际上是不会的,因为驱动的文件名不会超过8个字符),ZwQueryDirectoryFile返回非STATUS_NO_MORE_FILES的错误代号。这时,我们只简单的过滤掉这个文件。
        us.Length := pfdi^.FileNameLength;
            us.MaximumLength := pfdi^.FileNameLength;
            us.Buffer := PWideChar(@pfdi^.FileName);
            if RtlUnicodeStringToAnsiString(@_as, @us, true) = STATUS_SUCCESS then
            begin
              RtlTimeToTimeFields(@pfdi^.CreationTime, @tf);
              DbgPrint(' %s size=%d created on %d.%02d.%04d'#13#10,
                       _as.Buffer, pfdi^.EndOfFile.LowPart,
                       BYTE(tf.Day), BYTE(tf.Month), WORD(tf.Year));
              RtlFreeAnsiString(@_as);
            End;

格式化获得的信息,输出文件名、大小以及创建时间。
  RtnCode := ZwQueryDirectoryFile(hDriversDirectory, 0, nil, nil, @iosb,
                                     pfdi, cb, FileDirectoryInformation,
                                     true, nil, false);
        end; {End While}

在调用ZwQueryDirectoryFile的循环中,参数ReturnSingleEntry、FileName和RestartScan分别为为TRUE、NULL和FALSE。这就使得ZwQueryDirectoryFile能继续列举文件。
ExFreePool(pfdi);
      end;
      ZwClose(hDriversDirectory);
    end else
    begin
      DbgPrint('FileWorks: Can''t open drivers directory. Status: %08X', RtnCode);
    end;
ZwClose(hSystemRootDirectory);

回收所有用过的资源。
以下是DebugView里输出的调试信息,你可以看到驱动程序正常工作并完成了相关的文件和目录操作。
FileWorks: Entering DriverEntry 
 
FileWorks: Creating /??/c:/FileWorks directory 
FileWorks: Directory created 
 
FileWorks: Creating /??/c:/FileWorks/test.txt file 
FileWorks: File created 
 
FileWorks: Opening file for writing 
FileWorks: File openeded 
FileWorks: File was written 
 
FileWorks: Opening file for changing attributes 
FileWorks: File openeded 
FileWorks: File attributes were: 00000020 
FileWorks: Now file marked as read-only 
 
FileWorks: Opening file for reading 
FileWorks: File openeded 
FileWorks: File content: /=Data can be written to an open file/= 
 
FileWorks: Opening file for changing attributes 
FileWorks: File openeded 
FileWorks: File attributes were: 00000021 
FileWorks: Now file can be written or deleted 
 
FileWorks: Opening file to append data 
FileWorks: File openeded 
FileWorks: Data appended to the file 
 
FileWorks: Opening file for reading 
FileWorks: File openeded 
FileWorks: File content: /=Data can be written to an open file using ZwWriteFile/= 
 
FileWorks: Opening file to truncate 
FileWorks: File openeded 
FileWorks: EOF was: 00000035 
FileWorks: File truncated to its half size 
 
FileWorks: Opening file for reading 
FileWorks: File openeded 
FileWorks: File content: /=Data can be written to an /= 
 
FileWorks: Opening file for deletion 
FileWorks: File openeded 
FileWorks: File has been marked for deletion 
FileWorks: It should be deleted when the last open handle is closed 
 
FileWorks: Directory should be deleted 
 
FileWorks: Opening directory to enumerate files 
 
FileWorks: ---------- Starting enumerate files ---------- 
 cbidf2k.sys size=13952 created on 2.05.2008 
 cd20xrnt.sys size=7680 created on 2.05.2008 
 cdaudio.sys size=18688 created on 2.05.2008 
 cdfs.sys size=63744 created on 2.05.2008 
 cdr4_xp.sys size=2432 created on 5.06.2006 
 cdralw2k.sys size=2560 created on 5.06.2006 
 cdrom.sys size=62976 created on 2.05.2008 
 ch7xxnt5.dll size=15423 created on 2.05.2008 
 cinemst2.sys size=262528 created on 2.05.2008 
 classpnp.sys size=49536 created on 2.05.2008 
 cmbatt.sys size=13952 created on 2.05.2008 
 cmdide.sys size=6656 created on 2.05.2008 
 compbatt.sys size=10240 created on 2.05.2008 
 cpqarray.sys size=14976 created on 2.05.2008 
 cpqdap01.sys size=11776 created on 2.05.2008 
 crusoe.sys size=39552 created on 2.05.2008 
 cxthsfs2.cty size=129045 created on 2.05.2008 
FileWorks: ------------------------------------------------ 
 
FileWorks: Leaving DriverEntry

 

2009-08-07 08:31:00 xxagri 阅读数 373
  • DelphiXE10零基础实战快速入门

    通过课堂上一个一个的实战例子,演示DelphiXE10的用法,最后通过一个完整的通讯录程序,让学员初步掌握软件开发的全部流程 让零基础学员快速入门Delphi编程,快速掌握Delphi的使用方法,并能够制作出自己的软件来。为同学们下一步的提高打下坚实的基础。

    1044 人正在学习 去看看 陈城
Delphi驱动开发研究第九篇--文件与目录
 
[日期:2009-03-07] 作者:mickeylan 来源:Delphi驱动 点击:427
 

提供对文件的读写功能是操作系统的一项重要任务。我们来看一下NT家族的操作系统都为我们提供了那些功能。
9.1 核心句柄表
在开始讨论本文的主题之前,我们先来讨论一个重要的问题,我们之前并未对其给予应有的注意。为了取得对象的句柄需要填充OBJECT_ATTRIBUTES结构体——我们已经做过很多遍了,其样子如下:

InitializeObjectAttributes(oa, @g_usName,OBJ_CASE_INSENSITIVE,0, nil);

初始化了OBJECT_ATTRIBUTES后,我们调用函数创建或打开这个对象并获得其句柄(handle)。但这个句柄进入的是得到上下文的那个进程的句柄表。因为对于进程句柄表是其特有的,所以使用这个句柄就只能在进程自己的上下文中。例如,若是试图在其它进程的上下文中打开这个句柄的话,好的情况下会操作失败,而运气不好的话,要是在这个进程句柄表中有取值相同的句柄——要知道句柄只是一个32位的数(准确地讲是一个位的结构体)——就可能关闭其它对象。甚至如果获得的句柄是驱动句柄,但是是在用户进程中,句柄就会进入这个进程的句柄表并有可能有意或无意地在用户模式下使用对象。不希望的事情却总是发生,这样的情况常常出现。正是因此,内核组件和驱动程序有其特殊性,它们不喜欢使用句柄,而是使用reference to object,这样比较好,只需简单地使用指向内存中对象结构体的指针。为了统计对对象的引用,在对象的首部保存着一个引用计数(reference count)。如果需要像本例和上例中的那样访问对象,就要设计一个循环,在不同的上下文中对其进行访问,让系统将句柄放入核心句柄表中(kernel handle table)。
从Windows 2000开始,在系统中有了专门的核心句柄表。在这个表中的句柄只能内核模式下的任意进程上下文中访问,与进程特有的句柄不同。甚至于,比如说如果在System进程的上下文、在DriverEntry函数中获得句柄,则就不能在用户进程上下文中使用对象。System进程实现了自己私有的句柄表,其与核心句柄表不同。
对于在核心句柄表中的句柄,需要在调用InitializeObjectAttributes宏时显式地设置OBJ_KERNEL_HANDLE标志,形式如下:
InitializeObjectAttributes(oa, @g_usName, OBJ_KERNEL_HANDLE, 0,nil);

9.2 FileWorks驱动程序源代码
就像上一例中的驱动程序,本例的驱动程序的代码也是由几个独立的函数构成的:CreateDirectory、CreateFile、WriteFile、MarkAsReadOnly、ReadFile、UnmarkAsReadOnly、AppendFile、TruncateFile、DeleteFile、DeleteDirectory和EnumerateFiles。几乎所有的函数都是独立工作的。
unit FileWorks;

interface

uses
  nt_status, ntoskrnl, ntifs, native, winioctl, fcall, macros;

function _DriverEntry(pDriverObject:PDRIVER_OBJECT;
                      pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall;

implementation

var
  g_usDirName: UNICODE_STRING;
  g_usFileName: UNICODE_STRING;

procedure CreateDirectory;
var
  oa: OBJECT_ATTRIBUTES;
  iosb: IO_STATUS_BLOCK;
  hDirectory: THANDLE;
  RtnCode: NTSTATUS;
begin
  { 还记得吧, 传递给DbgPrint函数的用于格式化Unicode的代码(%C, %S, %lc, %ls, %wc, %ws, %wZ)只能在
    IRQL = PASSIVE_LEVEL下调用! }
  DbgPrint(#13#10'FileWorks: Creating %ws directory'#13#10, g_usDirName.Buffer);

  InitializeObjectAttributes(oa, @g_usDirName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwCreateFile(@hDirectory, SYNCHRONIZE, @oa, @iosb, nil,
                          FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF,
                          FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT,
                          nil, 0);
  if RtnCode = STATUS_SUCCESS then
  begin
    if iosb.Information = FILE_CREATED then
    begin
      DbgPrint('FileWorks: Directory created'#13#10);
    end else if iosb.Information = FILE_OPENED then
    begin
      DbgPrint('FileWorks: Directory exists and was opened'#13#10);
    end;
    ZwClose(hDirectory);
  end else
  begin
    DbgPrint('FileWorks: Can''t create directory. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure CreateFile;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  RtnCode: NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Creating %ws file'#13#10, g_usFileName.Buffer);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);

  RtnCode := ZwCreateFile(@hFile, SYNCHRONIZE, @oa, @iosb, nil,
                          FILE_ATTRIBUTE_NORMAL, 0,
                          FILE_CREATE,
                          FILE_SYNCHRONOUS_IO_NONALERT, nil, 0);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File created'#13#10);
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t create file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure WriteFile;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  RtnCode: NTSTATUS;
  g_szData: PAnsiChar;
begin
  DbgPrint(#13#10'FileWorks: Opening file for writing'#13#10);

  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwCreateFile(@hFile, FILE_WRITE_DATA + SYNCHRONIZE,
                          @oa, @iosb, nil, 0, FILE_SHARE_READ,
                          FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                          nil, 0);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    g_szData := 'Data can be written to an open file';
    //RtlInitUnicodeString(g_szData, 'Data can be written to an open file');
    RtnCode := ZwWriteFile(hFile, 0, nil, nil, @iosb, g_szData,
                           strlen(g_szData), nil, nil);
    if RtnCode = STATUS_SUCCESS then
    begin
      DbgPrint('FileWorks: File was written'#13#10);
    end else
    begin
      DbgPrint('FileWorks: Can''t write to the file. Status: %08X/n', RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure MarkAsReadOnly;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THandle;
  fbi:FILE_BASIC_INFORMATION;
  RtnCode:NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Opening file for changing attributes'#13#10);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwCreateFile(@hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SYNCHRONIZE,
                          @oa, @iosb, nil, 0, FILE_SHARE_READ,
                          FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                          nil, 0);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    RtnCode := ZwQueryInformationFile(hFile, @iosb, @fbi,
                                      sizeof(fbi),
                                      FileBasicInformation);
    if RtnCode = STATUS_SUCCESS then
    begin
      DbgPrint('FileWorks: File attributes were: %08X'#13#10, fbi.FileAttributes);
      fbi.FileAttributes := fbi.FileAttributes or FILE_ATTRIBUTE_READONLY;
      RtnCode := ZwSetInformationFile(hFile, @iosb, @fbi,
                                      sizeof(fbi), FileBasicInformation);
      if RtnCode = STATUS_SUCCESS then
      begin
        DbgPrint('FileWorks: Now file marked as read-only'#13#10);
      end else
      begin
        DbgPrint('FileWorks: Can''t change file attributes. Status: %08X'#13#10, RtnCode);
      end;
    end else
    begin
      DbgPrint('FileWorks: Can''t query file attributes. Status: %08X'#13#10, RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X/n', RtnCode);
  end;
end;

procedure ReadFile;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  p:PVOID;
  cb:DWORD;
  fsi:FILE_STANDARD_INFORMATION;
  RtnCode: NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Opening file for reading'#13#10);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwOpenFile(@hFile, FILE_READ_DATA + SYNCHRONIZE,
                        @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE,
                        FILE_SYNCHRONOUS_IO_NONALERT);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    RtnCode := ZwQueryInformationFile(hFile, @iosb, @fsi,
                                      sizeof(fsi),
                                      FileStandardInformation);
    if RtnCode = STATUS_SUCCESS then
    begin
      cb := fsi.EndOfFile.LowPart + 1;
      p := ExAllocatePool(PagedPool, cb);
      if p <> nil then
      begin
        memset(p, 0, cb);
        RtnCode := ZwReadFile(hFile, 0, nil, nil, @iosb, p, cb, nil, nil);
        if RtnCode = STATUS_SUCCESS then
        begin
          DbgPrint('FileWorks: File content: /=%s/='#13#10, p);
        end else
        begin
          DbgPrint('FileWorks: Can''t read from the file. Status: %08X'#13#10, RtnCode);
        end;
        ExFreePool(p);
      end else
      begin
        DbgPrint('FileWorks: Can''t allocate memory. Status: %08X'#13#10, RtnCode);
      end;
    end else
    begin
      DbgPrint('FileWorks: Can''t query file size. Status: %08X'#13#10, RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure UnmarkAsReadOnly;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  fbi:FILE_BASIC_INFORMATION;
  RtnCode:NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Opening file for changing attributes'#13#10);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwCreateFile(@hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SYNCHRONIZE,
                          @oa, @iosb, nil, 0, FILE_SHARE_READ,
                          FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                          nil, 0);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    RtnCode := ZwQueryInformationFile(hFile, @iosb, @fbi,
                                      sizeof(fbi), FileBasicInformation);
    if RtnCode = STATUS_SUCCESS then
    begin
      DbgPrint('FileWorks: File attributes were: %08X'#13#10, fbi.FileAttributes);
      fbi.FileAttributes := fbi.FileAttributes and (not FILE_ATTRIBUTE_READONLY);
      RtnCode := ZwSetInformationFile(hFile, @iosb, @fbi, sizeof(fbi), FileBasicInformation);
      if RtnCode = STATUS_SUCCESS then
      begin
        DbgPrint('FileWorks: Now file can be written or deleted'#13#10);
      end else
      begin
        DbgPrint('FileWorks: Can''t change file attributes. Status: %08X'#13#10, RtnCode);
      end;
    end else
    begin
      DbgPrint('FileWorks: Can''t query file attributes. Status: %08X'#13#10, RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure AppendFile;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  g_szDataToAppend:PAnsiChar;
  RtnCode:NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Opening file to append data'#13#10);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwOpenFile(@hFile, FILE_APPEND_DATA + SYNCHRONIZE,
                        @oa, @iosb, FILE_SHARE_READ,
                        FILE_SYNCHRONOUS_IO_NONALERT);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    g_szDataToAppend := ' using ZwWriteFile';
    RtnCode := ZwWriteFile(hFile, 0, nil, nil, @iosb, g_szDataToAppend,
                           strlen(g_szDataToAppend), nil, nil);
    if RtnCode = STATUS_SUCCESS then
    begin
      DbgPrint('FileWorks: Data appended to the file'#13#10);
    end else
    begin
      DbgPrint('FileWorks: Can''t append data to file. Status: %08X'#13#10, RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure TruncateFile;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  fsi:FILE_STANDARD_INFORMATION;
  feofi:FILE_END_OF_FILE_INFORMATION;
  RtnCode:NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Opening file to truncate'#13#10);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwOpenFile(@hFile, FILE_WRITE_DATA + SYNCHRONIZE,
                        @oa, @iosb, FILE_SHARE_READ,
                        FILE_SYNCHRONOUS_IO_NONALERT);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    RtnCode := ZwQueryInformationFile(hFile, @iosb, @fsi,
                                      sizeof(fsi),
                                      FileStandardInformation);
    if RtnCode = STATUS_SUCCESS then
    begin
      DbgPrint('FileWorks: EOF was: %08X'#13#10, fsi.EndOfFile.LowPart);
      feofi.EndOfFile.HighPart := 0;
      feofi.EndOfFile.LowPart := (fsi.EndOfFile.LowPart) shr 1;
      RtnCode := ZwSetInformationFile(hFile, @iosb, @feofi,
                                      sizeof(feofi),
                                      FileEndOfFileInformation);
      if RtnCode = STATUS_SUCCESS then
      begin
        DbgPrint('FileWorks: File truncated to its half size'#13#10);
      end else
      begin
        DbgPrint('FileWorks: Can''t truncate file. Status: %08X'#13#10, RtnCode);
      end;
    end else
    begin
      DbgPrint('FileWorks: Can''t query file info. Status: %08X'#13#10, RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure DeleteFile;
var
  oa:OBJECT_ATTRIBUTES;
  iosb:IO_STATUS_BLOCK;
  hFile:THANDLE;
  fdi:FILE_DISPOSITION_INFORMATION;
  RtnCode:NTSTATUS;
begin
  DbgPrint(#13#10'FileWorks: Opening file for deletion'#13#10);
  InitializeObjectAttributes(oa, @g_usFileName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwCreateFile(@hFile, _DELETE + SYNCHRONIZE,
                          @oa, @iosb, nil, 0, FILE_SHARE_DELETE,
                          FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                          nil, 0);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint('FileWorks: File openeded'#13#10);
    fdi.DeleteFile := True;
    RtnCode := ZwSetInformationFile(hFile, @iosb, @fdi,
                                    sizeof(fdi),
                                    FileDispositionInformation);
    if RtnCode = STATUS_SUCCESS then
    begin
      DbgPrint('FileWorks: File has been marked for deletion'#13#10);
      DbgPrint('FileWorks: It should be deleted when the last open handle is closed'#13#10);
    end else
    begin
      DbgPrint('FileWorks: Can''t mark file for deletion. Status: %08X'#13#10, RtnCode);
    end;
    ZwClose(hFile);
  end else
  begin
    DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure DeleteDirectory;
var
  oa:OBJECT_ATTRIBUTES;
  RtnCode:NTSTATUS;
begin
  InitializeObjectAttributes(oa, @g_usDirName,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwDeleteFile(@oa);
  if RtnCode = STATUS_SUCCESS then
  begin
    DbgPrint(#13#10'FileWorks: Directory should be deleted'#13#10);
  end else
  begin
    DbgPrint(#13#10'FileWorks: Can''t delete directory. Status: %08X'#13#10, RtnCode);
  end;
end;

procedure EnumerateFiles;
var
  oa:OBJECT_ATTRIBUTES;
  hSystemRootDirectory:THANDLE;
  hDriversDirectory:THANDLE;
  _as:ANSI_STRING;
  us:UNICODE_STRING;
  iosb:IO_STATUS_BLOCK;
  tf:TIME_FIELDS;
  cb:DWORD;
  pfdi:PFILE_DIRECTORY_INFORMATION;
  RtnCode:NTSTATUS;
  g_usTemp:UNICODE_STRING;
begin
  DbgPrint(#13#10'FileWorks: Opening directory to enumerate files'#13#10);
  RtlInitUnicodeString(g_usTemp, '/SystemRoot');
  InitializeObjectAttributes(oa, @g_usTemp,
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                             0, nil);
  RtnCode := ZwOpenFile(@hSystemRootDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE,
                        @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE,
                        FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT);
  if RtnCode = STATUS_SUCCESS then
  begin
    RtlInitUnicodeString(g_usTemp, 'system32/drivers');
    InitializeObjectAttributes(oa, @g_usTemp,
                               OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                               hSystemRootDirectory, nil);
    RtnCode := ZwOpenFile(@hDriversDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE,
                          @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE,
                          FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT);
    if RtnCode = STATUS_SUCCESS then
    begin
      cb := sizeof(FILE_DIRECTORY_INFORMATION) + 256;
      pfdi := ExAllocatePool(PagedPool, cb);
      if pfdi <> nil then
      begin
        RtlInitUnicodeString(g_usTemp, 'c*');
        DbgPrint(#13#10'FileWorks: ---------- Starting enumerate files ----------'#13#10);
        RtnCode := ZwQueryDirectoryFile(hDriversDirectory, 0, nil, nil, @iosb,
                                        pfdi, cb, FileDirectoryInformation,
                                        true, @g_usTemp, true);
        while RtnCode <> STATUS_NO_MORE_FILES do
        begin
          if RtnCode = STATUS_SUCCESS then
          begin
            us.Length := pfdi^.FileNameLength;
            us.MaximumLength := pfdi^.FileNameLength;
            us.Buffer := PWideChar(@pfdi^.FileName);
            if RtlUnicodeStringToAnsiString(@_as, @us, true) = STATUS_SUCCESS then
            begin
              RtlTimeToTimeFields(@pfdi^.CreationTime, @tf);
              DbgPrint(' %s size=%d created on %d.%02d.%04d'#13#10,
                       _as.Buffer, pfdi^.EndOfFile.LowPart,
                       BYTE(tf.Day), BYTE(tf.Month), WORD(tf.Year));
              RtlFreeAnsiString(@_as);
            end;
          end;
          RtnCode := ZwQueryDirectoryFile(hDriversDirectory, 0, nil, nil, @iosb,
                                          pfdi, cb, FileDirectoryInformation,
                                          true, nil, false);
        end; {End While}
        DbgPrint('FileWorks: ------------------------------------------------'#13#10);
        ExFreePool(pfdi);
      end;
      ZwClose(hDriversDirectory);
    end else
    begin
      DbgPrint('FileWorks: Can''t open drivers directory. Status: %08X', RtnCode);
    end;
    ZwClose(hSystemRootDirectory);
  end else
  begin
    DbgPrint('FileWorks: Can''t open system root directory. Status: %08X'#13#10, RtnCode);
  end;
end;

function _DriverEntry(pDriverObject:PDRIVER_OBJECT;
                      pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall;
begin
  DbgPrint(#13#10'FileWorks: Entering DriverEntry'#13#10);
  RtlInitUnicodeString(g_usFileName, '/??/c:/FileWorks/test.txt');
  RtlInitUnicodeString(g_usDirName, '/??/c:/FileWorks');
  CreateDirectory;
  CreateFile;
  WriteFile;
  MarkAsReadOnly;
  ReadFile;
  UnmarkAsReadOnly;
  AppendFile;
  ReadFile;
  TruncateFile;
  ReadFile;
  DeleteFile;
  DeleteDirectory;
  EnumerateFiles;
  DbgPrint(#13#10'FileWorks: Leaving DriverEntry'#13#10);
  result := STATUS_DEVICE_CONFIGURATION_ERROR;
end;

end.


9.3 创建目录与文件
InitializeObjectAttributes(oa, @g_usDirName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil);
填充OBJECT_ATTRIBUTES结构体,不要忘了OBJ_KERNEL_HANDLE标志。我想要强调的是,在本例中这个操作并不是必需的,因为我们不会在其它进程中使用这些句柄。但是因为FileWorks驱动程序的函数可以很容易的移植到使用任意进程上下文的程序中,所以我决定设置这个标志。如果在您的程序中有驱动和用户进程的共用的句柄,则不应设置此标志。如果只在自己一个进程上下文中使用对象,也不必设置OBJ_KERNEL_HANDLE标志。
创建目录和文件用的都是ZwCreateFile函数。从系统角度看,目录也是文件,所以创建目录的函数与创建文件的函数没有本质上的差别。所以,创建目录与创建文件使用同一个函数,只是创建目录要用FILE_DIRECTORY_FILE标志。
RtnCode := ZwCreateFile(@hDirectory, SYNCHRONIZE, @oa, @iosb, nil,FILE_ATTRIBUTE_NORMAL, 0, 
                                    FILE_OPEN_IF,FILE_DIRECTORY_FILE+FILE_SYNCHRONOUS_IO_NONALERT,
                                    nil, 0);

ZwCreateFile函数有相当多的参数,所以我在下面给出了它的原型,而且不得不使用了C语言的语义,以显示出输入(IN)、输出(OUT)和可选的参数。
NTSTATUS 
ZwCreateFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL,
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG CreateDisposition,
IN ULONG CreateOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength
);

函数成功完成后,参数FileHandle取得所建立的目录的句柄。DesiredAccess定义了对目录的三种访问请求。我们用标志SYNCHRONIZE来定义,这个值的含义在后面会清楚。ObjectAttributes想必您已经知道了。函数成功完成后,参数IoStatusBlock就是指向IO_STATUS_BLOCK结构体的指针,从中可以取出相关的的信息。参数FileAttributes定义了所创建目录的属性(只读、隐藏等)。我们使用FILE_ATTRIBUTE_NORMAL,本例中不需要为目录指定什么特别的属性。可选参数AllocationSize为0定义了所创建文件的大小为0。对于目录来说这是很自然的。参数ShareAccess定义为什么样的值,就要以什么样的权限访问目录。在本例中,我们不允许其它程序访问目录,故将此参数设为0。参数CreateDisposition定义了该目录已经存在,或者相反,文件不存在时系统的行为。我们使用标志FILE_OPEN_IF,这个标志表示如果目录已经存在,就将其打开。我们向参数CreateOptions传递的是标志FILE_DIRECTORY_FILE和FILE_SYNCHRONOUS_IO_NONALERT的组合。第一个表示要建立的是目录而非文件,无需特别解释。FILE_SYNCHRONOUS_IO_NONALERT定义了()对文件所有的操作都要是同步的,例如,调用ZwReadFile后在没有实际读取完文件数据时函数不会返回。在I/O管理器中为文件维护了一个当前文件位置上下文(file position context)。如果设置了标志FILE_SYNCHRONOUS_IO_NONALERT,则在参数DesiredAccess里应该定义为标志SYNCHRONIZE。最后两个参数不用于驱动程序。
if RtnCode = STATUS_SUCCESS then
begin
    if iosb.Information = FILE_CREATED then
    begin
      ……      
    end else if iosb.Information = FILE_OPENED then
    begin
      ……      
end;

正如我所讲的,在IO_STATUS_BLOCK结构体中会有额外的信息。
还记得我们是如何处理I/O请求的(见前面的章节)。例如,在驱动程序SharingMemory中对IRP_MJ_CREATE和IRP_MJ_CLOSE的处理如下:
p_Irp^.IoStatus.Status := STATUS_SUCCESS;
p_Irp^.IoStatus.Information := 0;
IofCompleteRequest(p_Irp, IO_NO_INCREMENT);

本例的驱动程序也大致相同,即将完成创建文件的请求。只有放入IO_STATUS_BLOCK结构体的域中的值会依赖于请求的类型。
因为我们定义了FILE_OPEN_IF标志,通过iosb.Information的值,我们可以知道是该建立新的目录还是因该目录已经存在而将其打开。
ZwClose(hDirectory);

我再重复一遍,在内核模式下一定要显式地关闭所有打开的句柄。
RtnCode := ZwCreateFile(@hFile, SYNCHRONIZE, @oa, @iosb, nil,
                     FILE_ATTRIBUTE_NORMAL, 0,
                     FILE_CREATE,
                     FILE_SYNCHRONOUS_IO_NONALERT, nil, 0);

如您所见,文件的创建实际上是相同的,只是要去掉FILE_DIRECTORY_FILE标志。而FILE_CREATE标志我将其用于多种情况。它表示只可以创建文件。如果文件已经存在,则对ZwCreateFile会以失败结束。

9.4 文件对象
每一个打开的文件句柄都对应着一个文件对象(file object),在内核内存区中有FILE_OBJECT结构体。
TFileObject=packed record
    wType:Word;
    Size:Word;
    DeviceObject:PDeviceObject;
    DoNotUser1:Pointer;
    FsContext:Pointer;
    FsContext2:Pointer;
    SectionObjectPointer:Pointer;
    PrivateCacheMap:Pointer;
    FinalStatus:NTSTATUS;
    RelatedFileObject:PFileObject;
    LockOperation:Boolean;
    DeletePending:Boolean;
    ReadAccess:Boolean;
    WriteAccess:Boolean;
    DeleteAccess:Boolean;
    SharedRead:Boolean;
    SharedWrite:Boolean;
    SharedDelete:Boolean;
    Flags:Cardinal;
    FileName:TUnicodeString;
    CurrentByteOffset:TLargeInteger;
    Waiters:Cardinal;
    Busy:Cardinal;
    LastLock:Pointer;
    Lock:TKEvent;
    Event:TKEvent;
   CompletionContext:Pointer;
end;

例如,我们可以两次打开同一个文件,但是两次的访问请求却不相同:第一次读(FILE_READ_DATA)而第二次写(FILE_WRITE_DATA)。结果内核会建立两个FILE_OBJECT结构体,每一个都对应于自己相应的文件句柄。但是两个句柄和相对应的两个FILE_OBJECT结构体都对应着同一个磁盘文件。第一个FILE_OBJECT结构体将设置ReadAccess域,第二个则设置WriteAccess域。例如,试图写入第一个句柄指向的文件时,系统会发现域WriteAccess = FALSE并结束请求。
DeviceObject域将包含指向设备对象/Device/HarddiskVolume1的指针,因为这个设备是对/??/c:符号链接,我们创建文件时在文件名中使用了/??/c:。
在读完本文后您可能会进行文件操作的实验并会发现系统是如何填充并管理FILE_OBJECT结构体的。如果在内存中定位它时发生问题,可以使用ObReferenceObjectByHandle函数,形式大致如下:
var
pFileObject:PFILE_OBJECT;
. . .
begin
RtnCode := ObReferenceObjectByHandle(hFile, FILE_READ_DATA, nil, KernelMode, @pFileObject,nil);
  if RtnCode = STATUS_SUCCESS then
begin
    {pFileObject指向对应于hFile的FILE_OBJECT}
    ObfDereferenceObject(pFileObject);
end;
end;

ObReferenceObjectByHandle向参数pFileObject中返回指向对应于该文件句柄的FILE_OBJECT结构体的指针。这时,引用计数会增一。之后一定要调用ObfDereferenceObject来使其复原。
9.5 写入文件
到这里我们已经有了大小为0目录和文件。该向文件中写点东西了。
RtnCode := ZwCreateFile(@hFile, FILE_WRITE_DATA + SYNCHRONIZE,
                     @oa, @iosb, nil, 0, FILE_SHARE_READ,
                     FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                     nil, 0);

借助于ZwCreateFile不止可以创建文件,还可以打开已有的文件(恰好,不只是文件,其它某些对象也是这样)。为此需要指定标志FILE_OPEN。
为了写入文件,需要相应的访问权——使用FILE_WRITE_DATA。
在单元文件中定义了几个常量以用于对文件的一般访问。例如,FILE_GENERIC_WRITE不仅允许向文件中写入数据,还可以更改其属性并添加数据。
FILE_GENERIC_WRITE = (STANDARD_RIGHTS_WRITE or
                                    FILE_WRITE_DATA or
                                    FILE_WRITE_ATTRIBUTES or
                                    FILE_WRITE_EA or
                                    FILE_APPEND_DATA or
                                    SYNCHRONIZE);

显然,当我们写文件,其它的程序不应向该文件写入。使用FILE_SHARE_READ标志,则没有程序能写入或是删除文件,而只能读取文件。
if RtnCode = STATUS_SUCCESS then
begin
    DbgPrint('FileWorks: File openeded'#13#10);
    g_szData := 'Data can be written to an open file';
    RtnCode := ZwWriteFile(hFile, 0, nil, nil, @iosb, g_szData,
                           strlen(g_szData), nil, nil);

函数ZwWriteFile见名则知意。
NTSTATUS 
ZwWriteFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID Buffer,
IN ULONG Length,
IN PLARGE_INTEGER ByteOffset OPTIONAL,
IN PULONG Key OPTIONAL
);

ZwWriteFile的参数至少要有文件句柄、IO_STATUS_BLOCK结构体指针——在该结构体中将放置额外的信息(其中包括向文件写入的字节数)、指向需要写入文件的数据的指针和其大小。
9.6 修改文件属性
我们需要防止我们的文件被删除。
RtnCode := ZwCreateFile(@hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES +  SNCHRONIZE, @oa, @iosb, nil, 0, FILE_SHARE_READ,
                          FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                          nil, 0);

标志FILE_READ_ATTRIBUTES和FILE_WRITE_ATTRIBUTES用于取得和更改相应的文件属性。
if RtnCode = STATUS_SUCCESS then
begin
  DbgPrint('FileWorks: File openeded'#13#10);
  RtnCode := ZwQueryInformationFile(hFile, @iosb, @fbi,
                                sizeof(fbi),
                                FileBasicInformation);
  if RtnCode = STATUS_SUCCESS then
  begin

我们需要建立只读属性,但由于文件有其它的属性需要保留,故有:
fbi.FileAttributes := fbi.FileAttributes or FILE_ATTRIBUTE_READONLY;
RtnCode := ZwSetInformationFile(hFile, @iosb, @fbi,
                            sizeof(fbi), FileBasicInformation);

要向文件属性中添加属性,我们必须要用标志。当需要根改文件时,我们用UnmarkAsReadOnly函数去掉这个属性,有:
fbi.FileAttributes := fbi.FileAttributes and (not FILE_ATTRIBUTE_READONLY);

9.7 读取文件
现在,为了各种操作,我们用ZwOpenFile函数打开文件来从中读取。所需要的参数与ZwCreateFile的类似。
NTSTATUS
ZwOpenFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG ShareAccess,
IN ULONG OpenOptions
);

RtnCode := ZwOpenFile(@hFile, FILE_READ_DATA + SYNCHRONIZE,
                        @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE,
                        FILE_SYNCHRONOUS_IO_NONALERT);

FILE_READ_DATA——在这里我们只从文件中读取数据。标志FILE_SHARE_READ、FILE_SHARE_WRITE和FILE_SHARE_DELETE的组合允许对文件进行完全的访问。
if RtnCode = STATUS_SUCCESS then
begin
  DbgPrint('FileWorks: File openeded'#13#10);
  RtnCode := ZwQueryInformationFile(hFile, @iosb, @fsi,
                                sizeof(fsi),
                                FileStandardInformation);
 if RtnCode = STATUS_SUCCESS then
  begin

我们以准备好读取文件的所有数据,但是用于这个请求的缓冲区的大小依赖于文件的大小。文件的大小可以用信息类FileStandardInformation由函数ZwQueryInformationFile获得,并将其传递给FILE_BASIC_INFORMATION指针。
cb := fsi.EndOfFile.LowPart + 1;

我们来为最后的零添加一个字节。
    
p := ExAllocatePool(PagedPool, cb);
      if p <> nil then
      begin
        memset(p, 0, cb);
        RtnCode := ZwReadFile(hFile, 0, nil, nil, @iosb, p, cb, nil, nil);
        if RtnCode = STATUS_SUCCESS then
        begin
          DbgPrint('FileWorks: File content: /=%s/='#13#10, p);
        End;
        ExFreePool(p);
     end;

分配所需的缓冲区,将其清零并传递给ZwReadFile函数。这个函数的原型与ZwWriteFile函数的原型类似,只是缓冲区用处不同。因为在使用之前我们清零了缓冲区,并且其大小比文件的内容多一个字节,所以不会发生将其内容输出到调试信息的问题。
9.8 向文件追加数据
向文件中追加数据有几种方法。可以用标志FILE_WRITE_DATA将其打开,在当前位置建立指向文件末尾的指针,在函数ZwWriteFile的参数ByteOffset中传递偏移量并写入数据。指示文件当前位置的指针保存在FILE_OBJECT.CurrentByteOffset中。我们还可以用标志FILE_APPEND_DATA打开文件,文件当前位置指针会自动指向其末尾。
RtnCode := ZwOpenFile(@hFile, FILE_APPEND_DATA + SYNCHRONIZE,
                    @oa, @iosb, FILE_SHARE_READ,
                    FILE_SYNCHRONOUS_IO_NONALERT);
if RtnCode = STATUS_SUCCESS then
begin
  DbgPrint('FileWorks: File openeded'#13#10);
  g_szDataToAppend := ' using ZwWriteFile';
  RtnCode := ZwWriteFile(hFile, 0, nil, nil, @iosb, g_szDataToAppend,
                      strlen(g_szDataToAppend), nil, nil);

文件的大小会自动增加。
9.9 截短文件
假设我们需要截短文件,去掉不需要的数据。在本例中,我为了简单起见,将文件缩减为原来的一半。
RtnCode := ZwOpenFile(@hFile, FILE_WRITE_DATA + SYNCHRONIZE,
                    @oa, @iosb, FILE_SHARE_READ,
                    FILE_SYNCHRONOUS_IO_NONALERT);

我们用写访问来打开文件。
if RtnCode = STATUS_SUCCESS then
begin
RtnCode := ZwQueryInformationFile(hFile, @iosb, @fsi,
                                 sizeof(fsi),
                                 FileStandardInformation);

我们在FILE_STANDARD_INFORMATION结构体的成员中可以获得文件当前的大小。
   
 feofi.EndOfFile.HighPart := 0;
      feofi.EndOfFile.LowPart := (fsi.EndOfFile.LowPart) shr 1;
      RtnCode := ZwSetInformationFile(hFile, @iosb, @feofi,
                                  sizeof(feofi),
                                  FileEndOfFileInformation);

使用信息类FileEndOfFileInformation,我们设置新的大小等于当前大小的一半。
9.10 删除文件与目录
小事一桩——将c:恢复为初始的状态。令人奇怪的是,2000 DDK里没有提到存在ZwDeleteFile函数。在XP DDK中说系统终归提供了这个函数,但是是从Windows XP开始的。但事实并非如此。Windows 2000甚至Windows NT4中就有ZwDeleteFile。但对于文件的删除,我们有几种更为复杂的方法,而对目录的删除则借助于ZwDeleteFile。
RtnCode := ZwCreateFile(@hFile, _DELETE + SYNCHRONIZE,
                     @oa, @iosb, nil, 0, FILE_SHARE_DELETE,
                     FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
                     nil, 0);

我们打开文件以进行删除。
if RtnCode = STATUS_SUCCESS then
begin
fdi.DeleteFile := True;
  RtnCode := ZwSetInformationFile(hFile, @iosb, @fdi,
                              sizeof(fdi),
                              FileDispositionInformation);

使用信息类FileDispositionInformation并设置文件删除标志。这时在FILE_OBJECT结构体的DeletePending域中的值由FALSE变为TRUE。这表示文件被标记为删除。这样只要存在该文件的一个打开的句柄,文件就不会被删除。
ZwClose(hFile);
现在文件唯一的句柄被关闭,文件被删除。
ZwDeleteFile(@oa);
使用ZwDeleteFile删除目录很简单,我就不多说了。

9.11 列举目录内容
一般说来,有两种方法可以用来定义创建/打开的对象的名字,而文件却特别。访问命名对象可以使用完整的路径,也可以使用符号链接。这里我们只使用绝对路径。例如,路径/??/c:/FileWorks/test.txt就是绝对路径。在本例中,使用
InitializeObjectAttributes宏填充InitializeObjectAttributes结构体,其形式如下:
InitializeObjectAttributes(oa, @g_usTemp,
                     OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                     0, nil);

倒数第二个参数RootDirectory为0。InitializeObjectAttributes的参数RootDirectory和该函数填充的OBJECT_ATTRIBUTES的同名域定义了目录容器对象的句柄。
OBJECT_ATTRIBUTES=record
. . .
  RootDirectory:THANDLE;
. . .
end;

如果目录容器对象已经打开,则已经取得了句柄,就可以用相对于目录容器的路径来使用对象。这时目录容器的句柄应该放置在RootDirectory中。例如,如果我们已经打开了目录/??/c:/FileWorks/并将其句柄放入了变量hDirectory中,则对test.txt的文件路径我们可以如下使用:
RtlInitUnicodeString(g_usFileName, '/??/c:/FileWorks/test.txt');
InitializeObjectAttributes(a,@g_usFileName, 
OBJ_CASE_INSENSITIVE, hDirectory, nil);

在目录容器下就意味着目录不只是在磁盘上,还在对象管理器的名字空间中。
对于列出系统目录/%SystemRoot%/System32/Drivers/的内容,我们来使用相对路径。
RtlInitUnicodeString(g_usTemp, '/SystemRoot');
InitializeObjectAttributes(oa, @g_usTemp,
                     OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                     0, nil);

我们来填充OBJECT_ATTRIBUTES结构体,使用符号链接/SystemRoot——这个是绝对路径。
RtnCode := ZwOpenFile(@hSystemRootDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE,
                        @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE,
                        FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT);

我们打开目录/%SystemRoot%/。FILE_LIST_DIRECTORY标志用于列举目录内容。
if RtnCode = STATUS_SUCCESS then
begin
  RtlInitUnicodeString(g_usTemp, 'system32/drivers');
  InitializeObjectAttributes(oa, @g_usTemp,
                       OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE,
                       hSystemRootDirectory, nil);

如果目录打开成功,从变量hSystemRootDirectory中我们可以取得其句柄,这个句柄将用作目录容器的句柄。记着OBJECT_ATTRIBUTES结构体使用相对路径“system32/drivers”。
RtnCode := ZwOpenFile(@hDriversDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE,
                          @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE,
                          FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT);
if RtnCode = STATUS_SUCCESS then
begin

我们来用相对路径打开目录/%SystemRoot%/System32/Drivers/。
cb := sizeof(FILE_DIRECTORY_INFORMATION) + 256;
pfdi := ExAllocatePool(PagedPool, cb);
if pfdi <> nil then
begin

分配必要的缓冲区,在缓冲区中应放得下FILE_DIRECTORY_INFORMATION结构体和文件名。
RtlInitUnicodeString(g_usTemp, 'c*');
RtnCode := ZwQueryDirectoryFile(hDriversDirectory, 0, nil, nil, @iosb,
                             pfdi, cb, FileDirectoryInformation,
                             true, @g_usTemp, true);

我们开始列举目录文件,这里用的是信息类FileDirectoryInformation。函数ZwQueryDirectoryFile的原型大概是多余的。
NTSTATUS 
ZwQueryDirectoryFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileName OPTIONAL,
IN BOOLEAN RestartScan
);

但这个函数在2000 DDK中也没有被提到。XP DDK说这个函数只从Windows XP开始提供。这同样也不是真的。
我们令参数ReturnSingleEntry为TRUE,这就使ZwQueryDirectoryFile函数只返回一个文件的信息,而且是第一个文件的。参数FileName指向带有搜索条件“c*”的字符串,即只列出文件名以“c”开头的文件。这样能缩短输出的调试信息。在第一次调用ZwQueryDirectoryFile时,我们将参数RestartScan设为TRUE。这就使得ZwQueryDirectoryFile函数开始检查目录的内容。
while RtnCode <> STATUS_NO_MORE_FILES do
begin

循环调用ZwQueryDirectoryFile直到ZwQueryDirectoryFile返回STATUS_NO_MORE_FILES,即目录中所有文件都已经列举。
if RtnCode = STATUS_SUCCESS then
begin

如果突然发现有文件的文件名超过256字节(实际上是不会的,因为驱动的文件名不会超过8个字符),ZwQueryDirectoryFile返回非STATUS_NO_MORE_FILES的错误代号。这时,我们只简单的过滤掉这个文件。
        
us.Length := pfdi^.FileNameLength;
            us.MaximumLength := pfdi^.FileNameLength;
            us.Buffer := PWideChar(@pfdi^.FileName);
            if RtlUnicodeStringToAnsiString(@_as, @us, true) = STATUS_SUCCESS then
            begin
              RtlTimeToTimeFields(@pfdi^.CreationTime, @tf);
              DbgPrint(' %s size=%d created on %d.%02d.%04d'#13#10,
                       _as.Buffer, pfdi^.EndOfFile.LowPart,
                       BYTE(tf.Day), BYTE(tf.Month), WORD(tf.Year));
              RtlFreeAnsiString(@_as);
            End;

格式化获得的信息,输出文件名、大小以及创建时间。
  RtnCode := ZwQueryDirectoryFile(hDriversDirectory, 0, nil, nil, @iosb,
                                     pfdi, cb, FileDirectoryInformation,
                                     true, nil, false);
        end; {End While}

在调用ZwQueryDirectoryFile的循环中,参数ReturnSingleEntry、FileName和RestartScan分别为为TRUE、NULL和FALSE。这就使得ZwQueryDirectoryFile能继续列举文件。
ExFreePool(pfdi);
      end;
      ZwClose(hDriversDirectory);
    end else
    begin
      DbgPrint('FileWorks: Can''t open drivers directory. Status: %08X', RtnCode);
    end;
ZwClose(hSystemRootDirectory);

回收所有用过的资源。
以下是DebugView里输出的调试信息,你可以看到驱动程序正常工作并完成了相关的文件和目录操作。
FileWorks: Entering DriverEntry 
 
FileWorks: Creating /??/c:/FileWorks directory 
FileWorks: Directory created 
 
FileWorks: Creating /??/c:/FileWorks/test.txt file 
FileWorks: File created 
 
FileWorks: Opening file for writing 
FileWorks: File openeded 
FileWorks: File was written 
 
FileWorks: Opening file for changing attributes 
FileWorks: File openeded 
FileWorks: File attributes were: 00000020 
FileWorks: Now file marked as read-only 
 
FileWorks: Opening file for reading 
FileWorks: File openeded 
FileWorks: File content: /=Data can be written to an open file/= 
 
FileWorks: Opening file for changing attributes 
FileWorks: File openeded 
FileWorks: File attributes were: 00000021 
FileWorks: Now file can be written or deleted 
 
FileWorks: Opening file to append data 
FileWorks: File openeded 
FileWorks: Data appended to the file 
 
FileWorks: Opening file for reading 
FileWorks: File openeded 
FileWorks: File content: /=Data can be written to an open file using ZwWriteFile/= 
 
FileWorks: Opening file to truncate 
FileWorks: File openeded 
FileWorks: EOF was: 00000035 
FileWorks: File truncated to its half size 
 
FileWorks: Opening file for reading 
FileWorks: File openeded 
FileWorks: File content: /=Data can be written to an /= 
 
FileWorks: Opening file for deletion 
FileWorks: File openeded 
FileWorks: File has been marked for deletion 
FileWorks: It should be deleted when the last open handle is closed 
 
FileWorks: Directory should be deleted 
 
FileWorks: Opening directory to enumerate files 
 
FileWorks: ---------- Starting enumerate files ---------- 
 cbidf2k.sys size=13952 created on 2.05.2008 
 cd20xrnt.sys size=7680 created on 2.05.2008 
 cdaudio.sys size=18688 created on 2.05.2008 
 cdfs.sys size=63744 created on 2.05.2008 
 cdr4_xp.sys size=2432 created on 5.06.2006 
 cdralw2k.sys size=2560 created on 5.06.2006 
 cdrom.sys size=62976 created on 2.05.2008 
 ch7xxnt5.dll size=15423 created on 2.05.2008 
 cinemst2.sys size=262528 created on 2.05.2008 
 classpnp.sys size=49536 created on 2.05.2008 
 cmbatt.sys size=13952 created on 2.05.2008 
 cmdide.sys size=6656 created on 2.05.2008 
 compbatt.sys size=10240 created on 2.05.2008 
 cpqarray.sys size=14976 created on 2.05.2008 
 cpqdap01.sys size=11776 created on 2.05.2008 
 crusoe.sys size=39552 created on 2.05.2008 
 cxthsfs2.cty size=129045 created on 2.05.2008 
FileWorks: ------------------------------------------------ 
 
FileWorks: Leaving DriverEntry

 
2011-08-26 15:29:25 sustzw 阅读数 567
  • DelphiXE10零基础实战快速入门

    通过课堂上一个一个的实战例子,演示DelphiXE10的用法,最后通过一个完整的通讯录程序,让学员初步掌握软件开发的全部流程 让零基础学员快速入门Delphi编程,快速掌握Delphi的使用方法,并能够制作出自己的软件来。为同学们下一步的提高打下坚实的基础。

    1044 人正在学习 去看看 陈城
   Delphi能不能开发Windows的驱动程序(这里的驱动程序当然不是指VxD了^_^)一直是广大Delphi fans关注的问题。姑且先不说能或者不能,我们先来看看用Delphi开发驱动程序需要解决哪些技术上问题。
   Delphi的链接器是无法生成Windows内核模式程序的,因此用delphi无法直接生成驱动程序。M$的链接器是可以生成Windows内核模式程序的,那么是否可以用Delphi生成目标文件,然后用M$链接呢?要这么做必须要解决以下的问题:
   Delphi生成的目标文件是OMF格式的,而M$ link虽然声称支持OMF格式的目标文件,但基本无用。最好能将OMF格式转换成COFF格式,EliCZ大侠的OMF2D正好可以解决这个问题。解决了目标格式的问题,一切都OK了吗?远没这么简单。继续之前,让我们先来看一下著名的DDDK吧。
   DDDK(Delphi Driver Development Kit)是The Hacker Defender Project team发布的一个用Delphi开发Windows驱动程序的工具包,目前最新版是0.0.4版。DDDK是将常用的驱动API用Delphi做了层包装放在DDDK单元中,就像下面这样: 
unit DDDK;

interface

const
  NtKernel='ntoskrnl.exe';
……
procedure IoCompleteRequest(Irp:PIrp;PriorityBoost:Integer); stdcall;
     ……
implementation
procedure krnlIoCompleteRequest(Irp:PIrp;PriorityBoost:Integer); stdcall; external NtKernel  name 'IoCompleteRequest';

procedure IoCompleteRequest(Irp:PIrp;PriorityBoost:Integer); stdcall; 
begin 
  krnlIoCompleteRequest(Irp,PriorityBoost); 
end;
……

然后在每次链接驱动文件之前,用omf2d对dddk.obj中需要引入的驱动API做以下的处理:
omf2d inc\DDDK.obj /U- /CEIoCompleteRequest=_IoCompleteRequest@8 2>nul将DDDK.obj中的IoCompleteRequest改成_IoCompleteRequest@8,为什么要这样做呢?那是因为诸如ntoskrnl.lib之类的导入库都是coff格式的,coff格式就是这样命名的。完成这步以后就可以调用m$ link将目标文件链接成驱动文件了。
   这样做虽然可以生成正确的驱动文件,但缺点也是明显的。将驱动API用delphi包装,这些用delphi包装的函数不管是否使用都会被链接到最终生成的驱动文件中,这样会增加驱动文件的尺寸,而且通过delphi的封装函数再去调用驱动API效率也会受影响,还有就是每次链接前都要用omf2d inc\DDDK.obj /U- /CEIoCompleteRequest=_IoCompleteRequest@8去转换delphi的目标文件,既麻烦又容易出错。有没有更好的办法呢?
   omf2d的工作就是将delphi的命名方法转换成coff的_xxxxxxx@xx格式,默认omf2d会去掉前导下划线和@xx后缀,可以用/U_*开关让omf2d不删除前导下划线,如果我们再有没有@xx后缀的导入库,那问题就简单多了。但m$并没有提供没有@xx后缀的导入库,那就让我们自己做一个吧^_^,其实很简单,比如我们要生成hal.dll的导入库,只需要编辑一个如下内容的hal.def文件:
LIBRARY        HAL.DLL

EXPORTS
       ExAcquireFastMutex                
       ExReleaseFastMutex                
       ExTryToAcquireFastMutex           
       HalAcquireDisplayOwnership        
       HalAdjustResourceList             
       HalAllProcessorsStarted
       ……

然后用LINK /LIB /MACHINE:IX86 /DEF:hal.def /OUT:hal.lib命令就可以生成我们需要的没有@xx后缀的导入库文件了。有了这个文件,再加上本人开发的rmcoff工具,事情就好办多了。下面就让我们开始用delphi来开发一个简单的驱动程序beeper吧。
   这个驱动程序是从Four-F的KmdKit里的beeper转换过来的,程序的目标就是通过访问端口让PC的扬声器发声,程序通过三种方法让扬声器发声,一种是直接访问端口,一种是调用hal.dll的READ_PORT_UCHAR和WRITE_PORT_UCHAR访问端口,第三种方法则是调用hal.dll的HalMakeBeep函数。 
unit beeper;

interface

uses windows, DDDK, hal;

function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;

implementation

const
     TIMER_FREQUENCY:DWORD = 1193167;     {1,193,167 Hz}
     OCTAVE:DWORD             = 2;           {octave multiplier}
     PITCH_C:DWORD            = 523;         {C           -     523,25 Hz}
     PITCH_Cs:DWORD           = 554;         {C#          -     554,37 Hz}
     PITCH_D:DWORD            = 587;         {D           -     587,33 Hz}
     PITCH_Ds:DWORD           = 622;         {D#          -     622,25 Hz}
     PITCH_E:DWORD            = 659;         {E           -     659,25 Hz}
     PITCH_F:DWORD            = 698;         {F           -     698,46 Hz}
     PITCH_Fs:DWORD           = 740;         {F#          -     739,99 Hz}
     PITCH_G:DWORD            = 784;         {G           -     783,99 Hz}
     PITCH_Gs:DWORD           = 831;         {G#          -     830,61 Hz}
     PITCH_A:DWORD            = 880;         {A           -     880,00 Hz}
     PITCH_As:DWORD           = 988;         {B           -     987,77 Hz}
     PITCH_H:DWORD            = 1047;        {H           - 1046,50 Hz}
     { We are going to play c-major chord }

     DELAY:DWORD              = $18000000;      {for my ~800mHz box}

     TONE_1:DWORD             = 1141;
     TONE_2:DWORD             = 905;
     TONE_3:DWORD             = 1568;      {for HalMakeBeep}

     STATUS_DEVICE_CONFIGURATION_ERROR:DWORD =     $00C0000182;

procedure MakeBeep1(dwPitch: DWORD); stdcall; assembler;
asm
     cli
     mov al, 10110110b
     out 43h, al
     mov eax, dwPitch
     out 42h, al
     mov al, ah
     out 42h, al
     {Turn speaker ON}
     in al, 61h
     or     al, 11b
     out 61h, al
     sti
     push eax
     mov eax, DELAY
@@1:
     dec eax
     jnz @@1
     pop eax
     cli
     {Turn speaker OFF}
     in al, 61h
     and al, 11111100b
     out 61h, al

     sti
end;

procedure MakeBeep2(dwPitch: DWORD); stdcall;
var
     dwPort, i: DWORD;
begin
     asm
       cli;
     end;
     WRITE_PORT_UCHAR(PUCHAR($43), $b6);
     WRITE_PORT_UCHAR(PUCHAR($42), dwPitch and $FF);
     WRITE_PORT_UCHAR(PUCHAR($42), ((dwPitch shr 8) and $FF));
     dwPort := READ_PORT_UCHAR(PUCHAR($61));
     dwPort := dwPort or 3;
     WRITE_PORT_UCHAR(PUCHAR($61), dwPort);
     asm
       sti
     end;
     for i := 1 to DELAY do
     begin
     end;
     asm
       cli
     end;
     { Turn speaker OFF }
     dwPort := READ_PORT_UCHAR(PUCHAR($61));
     dwPort := dwPort and $FC;
     WRITE_PORT_UCHAR(PUCHAR($61), dwPort);
     asm
       sti
     end;
end;

function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;
var
     i: integer;
begin
     MakeBeep1(TONE_1);
     MakeBeep2(TONE_2);
     HalMakeBeep(TONE_3);

     for i := 1 to DELAY do
     begin
     end;
     HalMakeBeep(0);
     Result := STATUS_DEVICE_CONFIGURATION_ERROR;
end;

end.


unit hal;

interface

uses
     Windows;

const
     NtHal = 'hal.dll';

function HalMakeBeep(Frequency: ULONG):BOOLEAN; stdcall;
function READ_PORT_UCHAR(Port:PUCHAR):UCHAR; stdcall;
procedure WRITE_PORT_UCHAR(Port: PUCHAR; Value: UCHAR); stdcall; 

implementation

function HalMakeBeep(Frequency: ULONG):BOOLEAN; stdcall; external NtHal name '_HalMakeBeep';
function READ_PORT_UCHAR(Port:PUCHAR):UCHAR; stdcall; external NtHal name '_READ_PORT_UCHAR';
procedure WRITE_PORT_UCHAR(Port: PUCHAR; Value: UCHAR); stdcall; external NtHal name '_WRITE_PORT_UCHAR';

end.

1.   用dcc32 –U ..\include -B -CG -JP -$A-,C-,D-,G-,H-,I-,L-,P-,V-,W+,Y- beeper.pas生成目标文件(此处的..\inc是我保存相关delphi单元文件的目录,你的可能不是这个目录哟)
2.   用rmcoff beeper.obj 处理delphi目标文件的相关符号并将目标文件从OMF格式转换成COFF格式,使其能被m$ link链接
3.   用link /NOLOGO /ALIGN:32 /BASE:0x10000 /SUBSYSTEM:NATIVE /DRIVER /ENTRY:DriverEntry ..\lib\hal.lib beeper.obj /OUT:beeper.sys生成最终的驱动文件。执行完以上的步骤,在你的目录下就会生成一个beeper.sys文件了。把它拷贝到KmdKit的beeper目录中,用它的SCP文件加载,PC的喇叭果然发出的清脆的声音,证明我们的delphi驱动是正确的。用此种方法生成的beeper.sys只有1376字节,只比用KmdKit的汇编代码的beeper.sys大几百个字节,而用DDDK生成的beeper.sys则要超过3K。
   打完这么多字真不容易,这篇教程就到这里吧,下一篇我们再来用delphi做一个更有趣的东东。
2011-08-26 15:34:20 sustzw 阅读数 4617
  • DelphiXE10零基础实战快速入门

    通过课堂上一个一个的实战例子,演示DelphiXE10的用法,最后通过一个完整的通讯录程序,让学员初步掌握软件开发的全部流程 让零基础学员快速入门Delphi编程,快速掌握Delphi的使用方法,并能够制作出自己的软件来。为同学们下一步的提高打下坚实的基础。

    1044 人正在学习 去看看 陈城
 上篇教程主要是讲解了用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:%08X)',DriverObject);
 DbgPrint('DriverUnload(-));
end;

function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;
begin
 DbgPrint('DriverEntry(DriverObject:%08X;RegistryPath:%08X)',DriverObject,RegistryPath);

 DriverObject^.DriverUnload:=@DriverUnload;

 Result:=STATUS_SUCCESS;
 DbgPrint('DriverEntry(-):%08X',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的写法可以参考Delphi驱动开发研究第二篇--工具及环境搭建 - fzwhk - fzwhk的个人主页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端口,这方面详细资料见Delphi驱动开发研究第二篇--工具及环境搭建 - fzwhk - fzwhk的个人主页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: %08X', 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) -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

  通过上面的两个例子的学习,相信大家已经能用Delphi写些基本的驱动程序了。本教程的第二部分也就到此为止了,后面还会有更精彩的内容。
没有更多推荐了,返回首页