实战 DeviceIoControl 之六:访问物理端口

Q: 在 NT/2000/XP 中,如何直接访问物理端口?

A : 看似小小问题,难倒多少好汉! 

   NT/2000/XP 从安全性、可靠性、稳定性上考虑,应用程序和操作系统是分开的,操作系统代码运行在核心态,有权访问系统数据和硬件,能执行特权指令;应用程序运行在用户 态,能够使用的接口和访问系统数据的权限都受到严格限制。当用户程序调用系统服务时,处理器捕获该调用,然后把调用的线程切换到核心态。当系统服务完成 后,操作系统将线程描述表切换回用户态,调用者继续运行。想在用户态应用程序中实现 I/O 读写,直接存取硬件,可以通过编写驱动程序,实现 CreateFile、CloseHandle、 DeviceIOControl、ReadFile、WriteFile 等功能。从 Windows 2000 开始,引入 WDM 核心态驱动程序的概念。

  下面是本人写的一个非常简单的驱动程序,可实现字节型端口 I/O。

#include <ntddk.h>
#include "MyPort.h"
// 设备类型定义
// 0-32767被Microsoft占用,用户自定义可用32768-65535
#define FILE_DEVICE_MYPORT  0x0000f000
// I/O控制码定义
// 0-2047被Microsoft占用,用户自定义可用2048-4095 
#define MYPORT_IOCTL_BASE 0xf00
#define IOCTL_MYPORT_READ_BYTE  CTL_CODE(FILE_DEVICE_MYPORT, MYPORT_IOCTL_BASE, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_MYPORT_WRITE_BYTE CTL_CODE(FILE_DEVICE_MYPORT, MYPORT_IOCTL_BASE+1, METHOD_BUFFERED, FILE_ANY_ACCESS)
// IOPM是65536个端口的位屏蔽矩阵,包含8192字节(8192 x 8 = 65536)
// 0 bit: 允许应用程序访问对应端口
// 1 bit: 禁止应用程序访问对应端口
#define IOPM_SIZE  8192
typedef UCHAR IOPM[IOPM_SIZE];
IOPM *pIOPM = NULL;
// 设备名(要求以UNICODE表示)
const WCHAR NameBuffer[] = L"DeviceMyPort";
const WCHAR DOSNameBuffer[] = L"DosDevicesMyPort";
// 这是两个在ntoskrnl.exe中的未见文档的服务例程
// 没有现成的已经说明它们原型的头文件,我们自己声明
void Ke386SetIoAccessMap(int, IOPM *);
void Ke386IoSetAccessProcess(PEPROCESS, int);
// 函数原型预先说明 
NTSTATUS MyPortDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
void MyPortUnload(IN PDRIVER_OBJECT DriverObject);
// 驱动程序入口,由系统自动调用,就像WIN32应用程序的WinMain
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
  PDEVICE_OBJECT deviceObject;
  NTSTATUS status;
  UNICODE_STRING uniNameString, uniDOSString;
  // 为IOPM分配内存
  pIOPM = MmAllocateNonCachedMemory(sizeof(IOPM));
  if (pIOPM == 0)
  {
     return STATUS_INSUFFICIENT_RESOURCES;
  }
  // IOPM全部初始化为0(允许访问所有端口)
  RtlZeroMemory(pIOPM, sizeof(IOPM));
  // 将IOPM加载到当前进程
  Ke386IoSetAccessProcess(PsGetCurrentProcess(), 1);
   Ke386SetIoAccessMap(1, pIOPM);
  // 指定驱动名字
  RtlInitUnicodeString(&uniNameString, NameBuffer);
  RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);
  // 创建设备
   status = IoCreateDevice(DriverObject, 0,
      &uniNameString,
       FILE_DEVICE_MYPORT,
      0, FALSE, &deviceObject);
  if (!NT_SUCCESS(status))
   {
    return status;
  }
  // 创建WIN32应用程序需要的符号连接
  status = IoCreateSymbolicLink (&uniDOSString, &uniNameString);
  if (!NT_SUCCESS(status))
   { 
    return status;
  }
  // 指定驱动程序有关操作的模块入口(函数指针)
  // 涉及以下两个模块:MyPortDispatch和MyPortUnload
   DriverObject->MajorFunction[IRP_MJ_CREATE]     =
   DriverObject->MajorFunction[IRP_MJ_CLOSE]     =
   DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyPortDispatch;
   DriverObject->DriverUnload = MyPortUnload;
  return STATUS_SUCCESS;
}
// IRP处理模块
NTSTATUS MyPortDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
  PIO_STACK_LOCATION IrpStack;
  ULONG       dwInputBufferLength;
  ULONG        dwOutputBufferLength;
  ULONG       dwIoControlCode;
  PULONG        pvIOBuffer;
  NTSTATUS      ntStatus;
  // 填充几个默认值
   Irp->IoStatus.Status = STATUS_SUCCESS;  // 返回状态
   Irp->IoStatus.Information = 0;      // 输出长度
  IrpStack = IoGetCurrentIrpStackLocation(Irp);
  // Get the pointer to the input/output buffer and it's length
  // 输入输出共用的缓冲区
  // 因为我们在IOCTL中指定了METHOD_BUFFERED,
  pvIOBuffer = Irp->AssociatedIrp.SystemBuffer;
  switch (IrpStack->MajorFunction)
  {
    case IRP_MJ_CREATE:    // 与WIN32应用程序中的CreateFile对应
      break;
    case IRP_MJ_CLOSE:    // 与WIN32应用程序中的CloseHandle对应
      break;
    case IRP_MJ_DEVICE_CONTROL:    // 与WIN32应用程序中的DeviceIoControl对应 
       dwIoControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
       switch (dwIoControlCode)
      {
        // 我们约定,缓冲区共两个DWORD,第一个DWORD为端口,第二个DWORD为数据
        // 一般做法是专门定义一个结构,此处简单化处理了
        case IOCTL_MYPORT_READ_BYTE:    // 从端口读字节
          pvIOBuffer[1] = _inp(pvIOBuffer[0]);
           Irp->IoStatus.Information = 8; // 输出长度为8
          break;
         case IOCTL_MYPORT_WRITE_BYTE:    // 写字节到端口
           _outp(pvIOBuffer[0], pvIOBuffer[1]);
          break;
         default:    // 不支持的IOCTL
          Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
      }
  }
  ntStatus = Irp->IoStatus.Status;
  IoCompleteRequest (Irp, IO_NO_INCREMENT);
   return ntStatus; 
}
// 删除驱动
void MyPortUnload(IN PDRIVER_OBJECT DriverObject)
{
  UNICODE_STRING uniDOSString;
   if(pIOPM)
  {
    // 释放IOPM占用的空间
     MmFreeNonCachedMemory(pIOPM, sizeof(IOPM));
  }
   RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);
   // 删除符号连接和设备
  IoDeleteSymbolicLink (&uniDOSString);
   IoDeleteDevice(DriverObject->DeviceObject);
}

下面给出实现设备驱动程序的动态加载的源码。动态加载的好处是,你不用做任何添加新硬件的操作,也不用编辑注册表,更不用重新启动计算机。

// 安装驱动并启动服务
// lpszDriverPath: 驱动程序路径
// lpszServiceName: 服务名 
BOOL StartDriver(LPCTSTR lpszDriverPath, LPCTSTR lpszServiceName)
{
   SC_HANDLE hSCManager;    // 服务控制管理器句柄
  SC_HANDLE hService;     // 服务句柄
  DWORD dwLastError;      // 错误码
  BOOL bResult = FALSE;    // 返回值
  // 打开服务控制管理器
  hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
  if (hSCManager)
  {
    // 创建服务
    hService = CreateService(hSCManager,
           lpszServiceName,
          lpszServiceName,
           SERVICE_ALL_ACCESS,
          SERVICE_KERNEL_DRIVER,
           SERVICE_DEMAND_START,
           SERVICE_ERROR_NORMAL,
           lpszDriverPath,
          NULL,
          NULL,
          NULL,
           NULL,
          NULL);
    if (hService == NULL)
    {
       if (::GetLastError() == ERROR_SERVICE_EXISTS)
      {
         hService = ::OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS);
      }
    }
    if (hService)
    {
       // 启动服务
      bResult = StartService(hService, 0, NULL);
       // 关闭服务句柄
       CloseServiceHandle(hService);
    }
    // 关闭服务控制管理器句柄
    CloseServiceHandle(hSCManager);
  }
  return bResult;
}
// 停止服务并卸下驱动
// lpszServiceName: 服务名 
BOOL StopDriver(LPCTSTR lpszServiceName)
{
  SC_HANDLE hSCManager;    // 服务控制管理器句柄
  SC_HANDLE hService;     // 服务句柄
   BOOL bResult;        // 返回值
  SERVICE_STATUS ServiceStatus;
   bResult = FALSE;
  // 打开服务控制管理器
   hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
  if (hSCManager)
   {
    // 打开服务
    hService = OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS);
    if (hService)
    {
       // 停止服务
      bResult = ControlService(hService, SERVICE_CONTROL_STOP, &ServiceStatus);
      // 删除服务
       bResult = bResult && DeleteService(hService);
      // 关闭服务句柄
       CloseServiceHandle(hService);
    }
    // 关闭服务控制管理器句柄
     CloseServiceHandle(hSCManager);
  }
  return bResult;
}
应用程 序实现端口 I/O 的接口如下:
// 全局的设备句柄
HANDLE hMyPort;
// 打开设备
// lpszDevicePath: 设备的路径
HANDLE OpenDevice(LPCTSTR lpszDevicePath)
{
   HANDLE hDevice;
  // 打开设备
  hDevice = ::CreateFile(lpszDevicePath,  // 设备路径
    GENERIC_READ | GENERIC_WRITE,    // 读写方式
    FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享方式
    NULL,          // 默认的安全描述符
    OPEN_EXISTING,      // 创建方式
    0,            // 不需设置文件属性
    NULL);          // 不需参照模板文件
   return hDevice;
}
// 打开端口驱动
BOOL OpenMyPort()
{
  BOOL bResult;
  // 设备名为"MyPort",驱动程序位于Windows的"system32drivers"目录中
   bResult = StartDriver("system32driversMyPort.sys", "MyPort");
  // 设备路径为".MyPort"
  if (bResult)
  {
    hMyPort = OpenDevice(".MyPort");
  }
  return (bResult && (hMyPort != INVALID_HANDLE_VALUE));
}
// 关闭端口驱动
BOOL CloseMyPort()
{
   return (CloseHandle(hMyPort) && StopDriver("MyPort"));
}
// 从指定端口读一个字节
// port: 端口
BYTE ReadPortByte(WORD port)
{
   DWORD buf[2];      // 输入输出缓冲区      
  DWORD dwOutBytes;    // IOCTL输出数据长度
  buf[0] = port;      // 第一个DWORD是端口
// buf[1] = 0;       // 第二个DWORD是数据
  // 用IOCTL_MYPORT_READ_BYTE读端口
   ::DeviceIoControl(hMyPort,  // 设备句柄
    IOCTL_MYPORT_READ_BYTE, // 取设备属性信息
    buf, sizeof(buf),    // 输入数据缓冲区
    buf, sizeof(buf),    // 输出数据缓冲区
    &dwOutBytes,       // 输出数据长度
     (LPOVERLAPPED)NULL);   // 用同步I/O 

在Windows NT/2K/XP中,直接用CreateFile打开名称类似于".A:"的”文件”,就可以与设备驱动打交道,通过ReadFile /WriteFile以绝对地址方式访问磁盘了。但Windows 9X不支持这样的简单方法。本文介绍一种在Windows 9X中实现磁盘直接访问的方法:利用系统的vwin32.vxd,通过DeviceIoControl调用DOS INT21 7305H与 440DH功能来完成。该调用支持FAT12、FAT16和FAT32,适用于Windows 95 SR2以及更高版本。

 

  先来了解 一下DOS INT21 7305H功能的入口参数:

AX -- 功能号7305HDS:BX -- 读写扇区的信息结构CX -- 必须为-1DL -- 驱动器号: 1=A:, 2=B:, 3=C:, ...SI -- 读写标志: 最低位0=读, 1=写

   若调用成功,清除C标志;否则设置C标志。 

  DS:BX指向一个结构,此结构定义如下:

DISKIO STRUC  dwStartSector  dd ?  ; 起始扇区  wSector     dw ?  ; 扇区数   lpBuffer    dd ?  ; 数据缓冲区地址DISKIO ENDS 在写操作下,需要“锁定”驱动器。DOS INT21 440DH 的4AH/6AH功能可实现逻辑驱动器的加锁/解锁。

  其入口参数为:

AX -- 功能号 440DHBH -- 锁的级别,0-3 级,直接写扇区用 1BL -- 驱动器号: 1=A:, 2=B:,
3=C:, ...CH -- 0x08CL -- 0x4ADX -- 0
AX -- 功能号440DHBL -- 驱动器号: 1=A:, 2=B:, 3=C:, ...CH -- 0x08CL -- 0x6A
以上两个调用,若调用成功,清除C标志;否则设置C标志。 

  通过 IOCTL码VWIN32_DIOC_DOS_DRIVEINFO 等调用上述中断。实现绝对磁盘读写的关键代码如下: 

// INT21 的IOCTL码

#define VWIN32_DIOC_DOS_IOCTL    1

#define VWIN32_DIOC_DOS_DRIVEINFO   6 // 寄存器组

typedef struct _DIOC_REGISTERS {  

DWORD reg_EBX;  

DWORD reg_EDX;  

DWORD reg_ECX;  

DWORD reg_EAX;  

DWORD reg_EDI;  
DWORD reg_ESI;  

DWORD reg_Flags;}

DIOC_REGISTERS, *PDIOC_REGISTERS; // IO参数(注意字节对齐方式)

#pragma pack(1)

typedef struct _DISKIO {  

DWORD dwStartSector;   //起始扇区  

WORD  wSectors;     // 扇区数  

void* pBuffer;      // 缓冲区指针

} DISKIO, *PDISKIO;

#pragma pack()

 

BOOL AbsDiskRead(BYTE nDiskNumber,// 盘号, 1=A:, 2=B:, 3= C:, ...  

DWORD dwStartSector,   // 起始扇区  

WORD wSectors,       // 扇区数  

void* pBuffer)      // 数据缓冲区指针
{  HANDLE hDevice;  DIOC_REGISTERS regs;  DISKIO dio;  DWORD dwOutBytes;  
BOOL bResult;   // 打开设备,获得VxD句柄  hDevice =
CreateFile(".vwin32",    // 设备路径    GENERIC_READ |
GENERIC_WRITE,      // 读写方式    FILE_SHARE_READ | FILE_SHARE_WRITE,   
// 共享方式    NULL,                  // 默认的安全描述符    
OPEN_EXISTING,              // 创建方式    FILE_ATTRIBUTE_NORMAL,         
// 文件属性    NULL);                  // 不需参照模板文件   if(hDevice == INVALID_HANDLE_VALUE)  {    return FALSE;  }   // 填充DISKIO
参数结构   dio.dwStartSector = dwStartSector;  dio.wSectors = wSectors;  
dio.pBuffer = pBuffer;   // 填充寄存器组--中断入口参数   memset(&regs, 0,
sizeof(DIOC_REGISTERS));   regs.reg_EAX = 0x7305;      // AX=0x7305  
regs.reg_EBX = (DWORD)&dio;   // EBX=DS:BX=参数指针  regs.reg_ECX = 0xffff;     
// CX=-1  regs.reg_EDX = nDiskNumber;   // DL=盘号  regs.reg_ESI = 0;        
// SI=0 -- 读操作   // 用VWIN32_DIOC_DOS_DRIVEINFO读磁盘  dwOutBytes = 0;  
bResult = DeviceIoControl(hDevice,      // 设备句柄    
VWIN32_DIOC_DOS_DRIVEINFO,         // INT21    &regs, sizeof(regs),          
// 输出数据缓冲区与长度    &regs, sizeof(regs),           // 输出数据
缓冲区与长度     &dwOutBytes,               // 输出数据长度    
NULL);                  // 用同步I/O   // 确定DeviceIoControl与
INT21 都无错误   bResult = bResult && !(regs.reg_Flags & 1);  
CloseHandle(hDevice);   return bResult;} BOOL AbsDiskWrite(  BYTE nDiskNumber,    
// 盘号, 1=A:, 2=B:, 3= C:, ...   DWORD dwStartSector,   // 起始扇区  WORD
wSectors,       // 扇区数  void* pBuffer)      // 数据缓冲区指针
{  HANDLE hDevice;  DIOC_REGISTERS regs;  DISKIO dio;  DWORD dwOutBytes;  
BOOL bResult;   // 打开设备,获得VxD句柄  hDevice =
CreateFile(".vwin32",    // 设备路径    GENERIC_READ |
GENERIC_WRITE,      // 读写方式    FILE_SHARE_READ | FILE_SHARE_WRITE,   
// 共享方式    NULL,                  // 默认的安全描述符    
OPEN_EXISTING,              // 创建方式    FILE_ATTRIBUTE_NORMAL,         
// 文件属性    NULL);                  // 不需参照模板文件  
if(hDevice == INVALID_HANDLE_VALUE)  {    return FALSE;  }   // 填充DISKIO
参数结构   dio.dwStartSector = dwStartSector;  dio.wSectors = wSectors;  
dio.pBuffer = pBuffer;   // 填充寄存器组--中断入口参数   memset(&regs, 0,
sizeof(DIOC_REGISTERS));   regs.reg_EAX = 0x7305;       // AX=0x7305  
regs.reg_EBX = (DWORD)&dio;    // EBX=DS:BX=参数指针  regs.reg_ECX = 0xffff;      
// CX=-1  regs.reg_EDX = nDiskNumber;    // DL=盘号  regs.reg_ESI = 0x6001;      
// SI=0x6001 -- 普通写操作   // 用VWIN32_DIOC_DOS_DRIVEINFO写磁盘  dwOutBytes = 0;  
bResult = DeviceIoControl(hDevice,      // 设备句柄    
VWIN32_DIOC_DOS_DRIVEINFO,         // INT21    &regs, sizeof(regs),          
// 输出数据缓冲区与长度    &regs, sizeof(regs),           // 输出数据
缓冲区与长度     &dwOutBytes,               // 输出数据长度    
NULL);                  // 用同步I/O   // 确定DeviceIoControl与
INT21 都无错误   bResult = bResult && !(regs.reg_Flags & 1);  
CloseHandle(hDevice);   return bResult;} BOOL LockVolume(  BYTE nDiskNumber)    
// 盘号, 1=A:, 2=B:, 3=C:, ... {  HANDLE hDevice;  DIOC_REGISTERS regs;  DWORD
dwOutBytes;   BOOL bResult;   // 打开设备,获得VxD句柄  hDevice =
CreateFile(".vwin32",     // 设备路径    GENERIC_READ |
GENERIC_WRITE,      // 读写方式    FILE_SHARE_READ | FILE_SHARE_WRITE,   
// 共享方式    NULL,                  // 默认的安全描述符    
OPEN_EXISTING,               // 创建方式    FILE_ATTRIBUTE_NORMAL,         
// 文件属性    NULL);                  // 不需参照模板文件   if(hDevice == INVALID_HANDLE_VALUE)  {    return FALSE;  }   // 填充寄存
器组--中断入口参数   memset(&regs, 0, sizeof(DIOC_REGISTERS));  regs.reg_EAX =
0x440D;             // AX=0x440D  regs.reg_EBX = 0x0100 | nDiskNumber;    
// BH=锁的级别,BL=盘号  regs.reg_ECX = 0x084A;  regs.reg_EDX = 0;   // 用
VWIN32_DIOC_DOS_DRIVEINFO 读磁盘  dwOutBytes = 0;  bResult =
DeviceIoControl(hDevice,      // 设备句柄    VWIN32_DIOC_DOS_IOCTL,         
// INT21    &regs, sizeof(regs),           // 输入数据缓冲区与长度    
&regs, sizeof(regs),           // 输出数据缓冲区与长度    &dwOutBytes,        
// 输出数据长度    NULL);                  // 用同步I/O   //
确定DeviceIoControl与 INT21 都无错误   bResult = bResult && !(regs.reg_Flags & 1);   
CloseHandle(hDevice);    return bResult;} BOOL UnlockVolume(  BYTE nDiskNumber)    
// 盘号, 1=A:, 2=B:, 3=C:, ... {  HANDLE hDevice;  DIOC_REGISTERS regs;  DWORD
dwOutBytes;   BOOL bResult;   // 打开设备,获得VxD句柄  hDevice =
CreateFile(".vwin32",     // 设备路径    GENERIC_READ |
GENERIC_WRITE,      // 读写方式    FILE_SHARE_READ | FILE_SHARE_WRITE,   
// 共享方式    NULL,                  // 默认的安全描述符    
OPEN_EXISTING,               // 创建方式    FILE_ATTRIBUTE_NORMAL,         
// 文件属性    NULL);                  // 不需参照模板文件   
if(hDevice == INVALID_HANDLE_VALUE)  {    return FALSE;  }   // 填充寄存
器组--中断入口参数   memset(&regs, 0, sizeof(DIOC_REGISTERS));  regs.reg_EAX =
0x440D;             // AX=0x440D  regs.reg_EBX = nDiskNumber;         
// BL=盘号  regs.reg_ECX = 0x086A;   // 用VWIN32_DIOC_DOS_DRIVEINFO读磁盘  
dwOutBytes = 0;  bResult = DeviceIoControl(hDevice,      // 设备句柄    
VWIN32_DIOC_DOS_IOCTL,           // INT21    &regs, sizeof(regs),          
// 输入数据缓冲区与长度    &regs, sizeof(regs),           // 输出数据
缓冲区与长度     &dwOutBytes,               // 输出数据长度    
NULL);                  // 用同步I/O   // 确定DeviceIoControl与
INT21 都无错误   bResult = bResult && !(regs.reg_Flags & 1);   
CloseHandle(hDevice);    return bResult;}
下面的例子,从A盘的0扇区开始,读取10个扇区的数据,并保存在文件中:
  unsigned  char b u f [512 *  10];   if (AbsDiskRead(1, 0, 10, buf))  {    FILE*
fp = fopen("a.dat", "w+b");    fwrite(buf, 512, 10, fp);    fclose(fp);  }
下 面的例子,读取D驱动器的第8888扇区,然后写回去:
  unsigned char buf[512];   LockVolume(4);  if (AbsDiskRead(4, 8888, 1, buf))  
{     ... ...    if (AbsDiskWrite(4, 8888, 1, buf))    
{      ... ...    }  }  UnlockVolume(4);
在写方式下,SI寄存器的位0设置为1,位15-13在磁盘的不同区域需要有 不同的值:

IRP 乱杂谈 

  作者: JIURL 

  主页: http://jiurl.yeah.net

   IRP 是 I/O request packet 的缩写,即 I/O 请求包。驱动与驱动之间通过 IRP 进行通信。而使用驱动的应用层调用的 CreatFile,ReadFile,WriteFile,DeviceIoControl 等函数, 说到底也是使用 IRP 和驱动进行通信。

   一个 IRP 由两部分组成。首先是头部或者叫包的固定部分,是一个 IRP 结构。紧跟在这个头部之后的是I/O stack locations ,这是一个 IO_STACK_LOCATION 结构的数组,这个数组中元素的个数是根据情况而定的,由 IoAllocateIrp( IN CCHAR StackSize , IN BOOLEAN ChargeQuota ) 时的参数 StackSize 决定。而 StackSize 通常由 IRP 发往的目标 DEVICE_OBJECT 的 +30 char StackSize 决定。而这个StackSize 是由设备对象连入所在的设备栈时,根据在设备栈中位置决定的。我们先看看 IRP 结构和IO_STACK_LOCATION 结构的定义。

   IRP 结构定义如下

struct _IRP (sizeof=112)
+00 int16 Type
+02 uint16 Size
+04 struct _MDL *MdlAddress
+08 uint32 Flags
+0c union __unnamed14 AssociatedIrp
+0c struct _IRP *MasterIrp
+0c int32 IrpCount
+0c void *SystemBuffer
+10 struct _LIST_ENTRY ThreadListEntry
+10 struct _LIST_ENTRY *Flink
+14 struct _LIST_ENTRY *Blink
+18 struct _IO_STATUS_BLOCK IoStatus
+18 int32 Status
+18 void *Pointer
+1c uint32 Information
+20 char RequestorMode
+21 byte PendingReturned
+22 char StackCount
+23 char CurrentLocation
+24 byte Cancel
+25 byte CancelIrql
+26 char ApcEnvironment
+27 byte AllocationFlags
+28 struct _IO_STATUS_BLOCK *UserIosb +2c struct _KEVENT *UserEvent
+30 union __unnamed15 Overlay
+30 struct __unnamed16 AsynchronousParameters
+30 function *UserApcRoutine
+34 void *UserApcContext
+30 union _LARGE_INTEGER AllocationSize
+30 uint32 LowPart
+34 int32 HighPart
+30 struct __unnamed3 u
+30 uint32 LowPart
+34 int32 HighPart
+30 int64 QuadPart
+38 function *CancelRoutine
+3c void *UserBuffer
+40 union __unnamed17 Tail
+40 struct __unnamed18 Overlay
+40 struct _KDEVICE_QUEUE_ENTRY DeviceQueueEntry
+40 struct _LIST_ENTRY DeviceListEntry
+40 struct _LIST_ENTRY *Flink
+44 struct _LIST_ENTRY *Blink
+48 uint32 SortKey
+4c byte Inserted
+40 void *DriverContext[4]
+50 struct _ETHREAD *Thread
+54 char *AuxiliaryBuffer
+58 struct _LIST_ENTRY ListEntry
+58 struct _LIST_ENTRY *Flink
+5c struct _LIST_ENTRY *Blink
+60 struct _IO_STACK_LOCATION *CurrentStackLocation
+60 uint32 PacketType
+64 struct _FILE_OBJECT *OriginalFileObject
+40 struct _KAPC Apc
+40 int16 Type
+42 int16 Size
+44 uint32 Spare0
+48 struct _KTHREAD *Thread
+4c struct _LIST_ENTRY ApcListEntry
+4c struct _LIST_ENTRY *Flink
+50 struct _LIST_ENTRY *Blink
+54 function *KernelRoutine
+58 function *RundownRoutine
+5c function *NormalRoutine
+60 void *NormalContext
+64 void *SystemArgument1 +68 void *SystemArgument2
+6c char ApcStateIndex
+6d char ApcMode
+6e byte Inserted
+40 void *CompletionKey

IO_STACK_LOCATION 结构定义如下

struct _IO_STACK_LOCATION (sizeof=36)
+00 byte MajorFunction
+01 byte MinorFunction
+02 byte Flags
+03 byte Control
+04 union __unnamed19 Parameters
+04 struct __unnamed20 Create
+04 struct _IO_SECURITY_CONTEXT *SecurityContext
+08 uint32 Options
+0c uint16 FileAttributes
+0e uint16 ShareAccess
+10 uint32 EaLength
+04 struct __unnamed21 CreatePipe
+04 struct _IO_SECURITY_CONTEXT *SecurityContext
+08 uint32 Options
+0c uint16 Reserved
+0e uint16 ShareAccess
+10 struct _NAMED_PIPE_CREATE_PARAMETERS *Parameters
+04 struct __unnamed22 CreateMailslot
+04 struct _IO_SECURITY_CONTEXT *SecurityContext
+08 uint32 Options
+0c uint16 Reserved
+0e uint16 ShareAccess
+10 struct _MAILSLOT_CREATE_PARAMETERS *Parameters
+04 struct __unnamed23 Read
+04 uint32 Length
+08 uint32 Key
+0c union _LARGE_INTEGER ByteOffset
+0c uint32 LowPart
+10 int32 HighPart
+0c struct __unnamed3 u
+0c uint32 LowPart
+10 int32 HighPart
+0c int64 QuadPart
+04 struct __unnamed23 Write
+04 uint32 Length +08 uint32 Key
+0c union _LARGE_INTEGER ByteOffset
+0c uint32 LowPart
+10 int32 HighPart
+0c struct __unnamed3 u
+0c uint32 LowPart
+10 int32 HighPart
+0c int64 QuadPart
+04 struct __unnamed24 QueryDirectory
+04 uint32 Length
+08 struct _STRING *FileName
+0c int32 FileInformationClass
+10 uint32 FileIndex
+04 struct __unnamed25 NotifyDirectory
+04 uint32 Length
+08 uint32 CompletionFilter
+04 struct __unnamed26 QueryFile
+04 uint32 Length
+08 int32 FileInformationClass
+04 struct __unnamed27 SetFile
+04 uint32 Length
+08 int32 FileInformationClass
+0c struct _FILE_OBJECT *FileObject
+10 byte ReplaceIfExists
+11 byte AdvanceOnly
+10 uint32 ClusterCount
+10 void *DeleteHandle
+04 struct __unnamed28 QueryEa
+04 uint32 Length
+08 void *EaList
+0c uint32 EaListLength
+10 uint32 EaIndex
+04 struct __unnamed29 SetEa
+04 uint32 Length
+04 struct __unnamed30 QueryVolume
+04 uint32 Length
+08 int32 FsInformationClass
+04 struct __unnamed30 SetVolume
+04 uint32 Length
+08 int32 FsInformationClass
+04 struct __unnamed31 FileSystemControl
+04 uint32 OutputBufferLength
+08 uint32 InputBufferLength
+0c uint32 FsControlCode +10 void *Type3InputBuffer
+04 struct __unnamed32 LockControl
+04 union _LARGE_INTEGER *Length
+08 uint32 Key
+0c union _LARGE_INTEGER ByteOffset
+0c uint32 LowPart
+10 int32 HighPart
+0c struct __unnamed3 u
+0c uint32 LowPart
+10 int32 HighPart
+0c int64 QuadPart
+04 struct __unnamed33 DeviceIoControl
+04 uint32 OutputBufferLength
+08 uint32 InputBufferLength
+0c uint32 IoControlCode
+10 void *Type3InputBuffer
+04 struct __unnamed34 QuerySecurity
+04 uint32 SecurityInformation
+08 uint32 Length
+04 struct __unnamed35 SetSecurity
+04 uint32 SecurityInformation
+08 void *SecurityDescriptor
+04 struct __unnamed36 MountVolume
+04 struct _VPB *Vpb
+08 struct _DEVICE_OBJECT *DeviceObject
+04 struct __unnamed36 VerifyVolume
+04 struct _VPB *Vpb
+08 struct _DEVICE_OBJECT *DeviceObject
+04 struct __unnamed37 Scsi
+04 *Srb
+04 struct __unnamed38 QueryQuota
+04 uint32 Length
+08 void *StartSid
+0c struct _FILE_GET_QUOTA_INFORMATION *SidList
+10 uint32 SidListLength
+04 struct __unnamed29 SetQuota
+04 uint32 Length
+04 struct __unnamed39 QueryDeviceRelations
+04 int32 Type
+04 struct __unnamed40 QueryInterface
+04 struct _GUID *InterfaceType
+08 uint16 Size
+0a uint16 Version
+0c struct _INTERFACE *Interface +10 void *InterfaceSpecificData
+04 struct __unnamed41 DeviceCapabilities
+04 struct _DEVICE_CAPABILITIES *Capabilities
+04 struct __unnamed42 FilterResourceRequirements
+04 struct _IO_RESOURCE_REQUIREMENTS_LIST *IoResourceRequirementList
+04 struct __unnamed51 ReadWriteConfig
+04 uint32 WhichSpace
+08 void *Buffer
+0c uint32 Offset
+10 uint32 Length
+04 struct __unnamed52 SetLock
+04 byte Lock
+04 struct __unnamed53 QueryId
+04 int32 IdType
+04 struct __unnamed54 QueryDeviceText
+04 int32 DeviceTextType
+08 uint32 LocaleId
+04 struct __unnamed55 UsageNotification
+04 byte InPath
+05 byte Reserved[3]
+08 int32 Type
+04 struct __unnamed56 WaitWake
+04 int32 PowerState
+04 struct __unnamed57 PowerSequence
+04 struct _POWER_SEQUENCE *PowerSequence
+04 struct __unnamed58 Power
+04 uint32 SystemContext
+08 int32 Type
+0c union _POWER_STATE State
+0c int32 SystemState
+0c int32 DeviceState
+10 int32 ShutdownType
+04 struct __unnamed59 StartDevice
+04 struct _CM_RESOURCE_LIST *AllocatedResources
+08 struct _CM_RESOURCE_LIST *AllocatedResourcesTranslated
+04 struct __unnamed60 WMI
+04 uint32 ProviderId
+08 void *DataPath
+0c uint32 BufferSize
+10 void *Buffer
+04 struct __unnamed61 Others
+04 void *Argument1
+08 void *Argument2
+0c void *Argument3 +10 void *Argument4
+14 struct _DEVICE_OBJECT *DeviceObject
+18 struct _FILE_OBJECT *FileObject
+1c function *CompletionRoutine
+20 void *Context

IO_STACK_LOCATION 结构的大小是固定的,+04 到 +14 之间的16个字节是个公用体。

  我们大略的看一个 例子,或许能让你对 IO_STACK_LOCATION 有点概念一个驱动程序的应用层程序,调用 DeviceIoControl ,让驱动程序完成一个任务。DeviceIoControl 中会申请一个Irp,初始化它,然后用这个 Irp,调用 IoCallDriver 发往设备栈的最高层。IoCallDriver 会根据Irp中的当前 IO_STACK_LOCATION 中的 +00 byte MajorFunction,调用发往设备对象所属驱动中的相应函数。

  我们看看被传入到设备栈最高层的 irp,

kd> !irp fe403968
Irp is active with 6 stacks 6 is current (= 0xfe403a8c)
No Mdl System buffer = fe3d6068 Thread fe427960: Irp stack trace. 
cmd flg cl Device File Completion-Context
[ 0, 0] 0 0 00000000 00000000 00000000-00000000 
Args: 00000000 00000000 00000000 00000000
[ 0, 0] 0 0 00000000 00000000 00000000-00000000 
Args: 00000000 00000000 00000000 00000000
[ 0, 0] 0 0 00000000 00000000 00000000-00000000 
Args: 00000000 00000000 00000000 00000000
[ 0, 0] 0 0 00000000 00000000 00000000-00000000 
Args: 00000000 00000000 00000000 00000000
[ 0, 0] 0 0 00000000 00000000 00000000-00000000 
Args: 00000000 00000000 00000000 00000000
>[ e, 0] 0 0 fe4f5df0 fe426688 00000000-00000000 
DriverKbdclass
Args: 00000000 00000004 000b0008 00000000

  WinDbg 的 !irp 命令,会显示一个 Irp 的 IO_STACK_LOCATION 数组的一些格式化信息可以看到传入的这个 Irp,它的最后一个 IO_STACK_LOCATION 是当前的 IO_STACK_LOCATION,里面有传入的一些参数。

>[ e, 0] 0 0 fe4f5df0 fe426688 00000000-00000000 
DriverKbdclass
Args: 00000000 00000004 000b0008 00000000
kd> !strct io_stack_location fe403a8c
struct _IO_STACK_LOCATION (sizeof=36)
+00 byte MajorFunction = 0e .
+01 byte MinorFunction = 00 .
+02 byte Flags = 00 .
+03 byte Control = 00 .
+04 union __unnamed19 Parameters
+04 struct __unnamed33 DeviceIoControl
+04 uint32 OutputBufferLength = 00000000
+08 uint32 InputBufferLength = 00000004
+0c uint32 IoControlCode = 000b0008
+10 void *Type3InputBuffer = 00000000
+14 struct _DEVICE_OBJECT *DeviceObject = FE4F5DF0
+18 struct _FILE_OBJECT *FileObject = FE426688
+1c function *CompletionRoutine = 00000000
+20 void *Context = 00000000

  这些就是当前 IO_STACK_LOCATION,里面有参数什么的,这样本层驱动就可以根据 IRP 和当前的IO_STACK_LOCATION 中的内容,知道该干些什么。本例中,本层驱动根据这个 Irp 做了些处理,发现还需要下层驱动做更多处理,就设置了下一层驱动要使用的 IO_STACK_LOCATION ,然后调用 IoCallDriver ,把这个 Irp 发到了下一层。

  在下一层的处理程序 中,我们看看收到的 Irp

kd> !irp fe403968
Irp is active with 6 stacks 5 is current (= 0xfe403a68)
No Mdl System buffer = fe3d6068 Thread fe427960: Irp stack trace. 
cmd flg cl Device File Completion-Context
[ 0, 0] 0 0 00000000 00000000 00000000-00000000 
Args: 00000000 00000000 00000000 00000000
[ 0, 0] 0 0 00000000 00000000 00000000-00000000 
Args: 00000000 00000000 00000000 00000000
[ 0, 0] 0 0 00000000 00000000 00000000-00000000 
Args: 00000000 00000000 00000000 00000000
[ 0, 0] 0 0 00000000 00000000 00000000-00000000 
Args: 00000000 00000000 00000000 00000000
>[ f, 0] 0 0 fe4f5020 fe426688 00000000-00000000 
Driveri8042prt
Args: 00000000 00000004 000b0008 00000000
[ e, 0] 0 0 fe4f5df0 fe426688 00000000-00000000 
DriverKbdclass
Args: 00000000 00000004 000b0008 00000000

我们看到了 Irp 的当前 IO_STACK_LOCATION 变成了倒数第二个IO_STACK_LOCATION ,里面是上一层驱动中设置的参数,告诉这一层驱动要干什么。本例中,本层驱动读写端口,操作硬件,完成了相应的功能。

   IoCallDriver

  首先我们看看对了解 IRP 最重要的一个函数,也是使用 IRP 进行驱动间通信所使用的函数, IoCallDriver。

  这个函数并不大,也不复杂,我们将详细的看看它的汇编代码。

NTSTATUS 
IoCallDriver(
IN PDEVICE_OBJECT DeviceObject,
IN OUT PIRP Irp
);
Irp 是发往设备对象的。参数 DeviceObject 指明设备目标设备对象。
nt!IoCallDriver:
80421417 8b542408 mov edx,[esp+0x8]
/* [esp+0x8] 为 IN OUT PIRP Irp */
8042141b 8b4c2404 mov ecx,[esp+0x4]
/* [esp+0x4] 为 IN PDEVICE_OBJECT DeviceObject */
8042141f e82ae1ffff call nt!IofCallDriver (8041f54e)
80421424 c20800 ret 0x8
nt!IofCallDriver:
8041f54e ff25d01a4780 jmp dword ptr [nt!pIofCallDriver (80471ad0)]
nt!IopfCallDriver:
8041f516 56 push esi
8041f517 8bf2 mov esi,edx
/* edx 为 IN OUT PIRP Irp */
8041f519 57 push edi
8041f51a 8bf9 mov edi,ecx
/* ecx 为 IN PDEVICE_OBJECT DeviceObject */
8041f51c fe4e23 dec byte ptr [esi+0x23] /* esi 为 IN OUT PIRP Irp */
/* struct _IRP (sizeof=112) */
/* +23 char CurrentLocation */
8041f51f 8a4623 mov al,[esi+0x23]
8041f522 33c9 xor ecx,ecx
8041f524 3ac1 cmp al,cl
8041f526 7f0b jg nt!IopfCallDriver+0x1d (8041f533)
/* 如果 Irp->CurrentLocation 大于0,就跳 */
8041f528 51 push ecx
8041f529 51 push ecx
8041f52a 51 push ecx
8041f52b 56 push esi
8041f52c 6a35 push 0x35
8041f52e e8e1c90000 call nt!KeBugCheckEx (8042bf14)
8041f533 8b4660 mov eax,[esi+0x60]
/* esi 为 IN OUT PIRP Irp */
/* struct _IRP (sizeof=112) */
/* +60 struct _IO_STACK_LOCATION *CurrentStackLocation */
8041f536 56 push esi
/* esi 为 IN OUT PIRP Irp */
8041f537 83e824 sub eax,0x24
/* eax 为 Irp->CurrentStackLocation */
/* 0x24 为 sizeof(IO_STACK_LOCATION) */
8041f53a 57 push edi
/* edi 为 IN PDEVICE_OBJECT DeviceObject */
8041f53b 894660 mov [esi+0x60],eax
/* [esi+0x60] 为 Irp->CurrentStackLocation */
8041f53e 897814 mov [eax+0x14],edi
/* eax 为新的当前 IO_STACK_LOCATION */
/* struct _IO_STACK_LOCATION (sizeof=36) */
/* +14 struct _DEVICE_OBJECT *DeviceObject */
/* edi 为 IN PDEVICE_OBJECT DeviceObject */
8041f541 8b4f08 mov ecx,[edi+0x8]
/* edi 为 IN PDEVICE_OBJECT DeviceObject */
/* struct _DEVICE_OBJECT (sizeof=184) */
/* +08 struct _DRIVER_OBJECT *DriverObject */
8041f544 0fb600 movzx eax,byte ptr [eax]
/* eax 为新的当前 IO_STACK_LOCATION */
/* struct _IO_STACK_LOCATION (sizeof=36) */
/* +00 byte MajorFunction */
8041f547 ff548138 call dword ptr [ecx+eax*4+0x38]
/* ecx 为 DeviceObject->DriverObject */
/* struct _DRIVER_OBJECT (sizeof=168) */
/* +38 function *MajorFunction[28] */ /* eax 为 Irp->CurrentStackLocation->MajorFunction */
/* [ecx+eax*4+0x38] 为 */
/* DeviceObject->DriverObject.MajorFunction[Irp->CurrentStackLocation->MajorFunction] */
8041f54b 5f pop edi
8041f54c 5e pop esi
8041f54d c3 ret

IoCallDriver 将参数 Irp 的 +23 char CurrentLocation 减1,然后检查这时的 CurrentLocation 是否大于0。如果不大于 0,将蓝屏。接着将参数 Irp 的 +60 struct _IO_STACK_LOCATION *CurrentStackLocation 减 0x24 。这里原来是一个 IO_STACK_LOCATION 的地址,0x24是一个 IO_STACK_LOCATION 结构的大小。这样减 0x24 就等于把 +60 struct _IO_STACK_LOCATION *CurrentStackLocation 指向了前一个 IO_STACK_LOCATION。然后将新的 CurrentStackLocation 的 +14 struct _DEVICE_OBJECT *DeviceObject 赋值为传入的参数 DeviceObject 。从传入的参数 DeviceObject 中获得该 DeviceObject 的 DriverObject,调用该 DriverObject 的新的CurrentStackLocation 的 +00 byte MajorFunction 相应的 MajorFunction ,用传入的两个参数 IN PDEVICE_OBJECT DeviceObject,IN OUT PIRP Irp 做参数。

  简单的说,把 Irp 的 CurrentLocation 减1,CurrentStackLocation 指向前一个(地址更小处) 的IO_STACK_LOCATION。然后根据现在的 CurrentStackLocation 所指的 IO_STACK_LOCATION 结构中 MajorFunction 的值,调用 DeviceObject 所属的驱动的 DriverObject 中 MajorFunction[28] 相应的函数。

   DriverObject 中的 MajorFunction[28],是在 DriverEntry 中初始化的。

  Irp 的产生

   Irp 说到底是由 IoAllocateIrp 产生的。IoBuildDeviceIoControlRequest 这样产生 Irp 的函数,也是调用IoAllocateIrp 实现的。一个驱动可能需要另一层的驱动完成什么功能,而直接使用IoBuildDeviceIoControlRequest 这样函数产生 Irp,并用这个 Irp 和其他驱动通信。更多的 Irp 是应用层,使用ReadFile,WriteFile,DeviceIoControl 等这样的 api,来产生的。这些 api 最终也会调用 IoAllocateIrp 产生一个 Irp,然后初始化这个 Irp,发给驱动,驱动会根据 Irp ,完成相应的任务。

Irp 操作的一些函数

  操作Irp的一些函数,很简单,只是一些宏。了解这些函数的具体的操作,有助于对 Irp 的了解。

   下面的内容来自 ntddk.h

IoGetCurrentIrpStackLocation
#define IoGetCurrentIrpStackLocation( Irp ) ( (Irp)->Tail.Overlay.CurrentStackLocation )
IoSkipCurrentIrpStackLocation
#define IoSkipCurrentIrpStackLocation( Irp )
(Irp)->CurrentLocation++; (Irp)->Tail.Overlay.CurrentStackLocation++;
IoGetNextIrpStackLocation
#define IoGetNextIrpStackLocation( Irp ) (
(Irp)->Tail.Overlay.CurrentStackLocation - 1 )
IoCopyCurrentIrpStackLocationToNext
#define IoCopyCurrentIrpStackLocationToNext( Irp ) {
PIO_STACK_LOCATION irpSp;
PIO_STACK_LOCATION nextIrpSp;
irpSp = IoGetCurrentIrpStackLocation( (Irp) );
nextIrpSp = IoGetNextIrpStackLocation( (Irp) );
RtlCopyMemory( nextIrpSp, irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine));
nextIrpSp->Control = 0; }
IoMarkIrpPending
#define IoMarkIrpPending( Irp ) (
IoGetCurrentIrpStackLocation( (Irp) )->Control |= SL_PENDING_RETURNED )

  Irp 的释放

   使用 IoFreeIrp 释放一个 Irp

  Irp 的结束

  使用 IoCompleteRequest 结束一个 Irp,这个是了解如何使用 Irp 通信中,除了 IoCallDriver 外,第二重要的函数,这个函数又大又复杂,牵涉到其他的一些机制,对自己,还是下不了这个毒手,所以就不说了,用一用也就有个大概的概念。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值