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:,
以上两个调用,若调用成功,清除C标志;否则设置C标志。
3=C:, ...CH --
0x08CL -- 0x4ADX -- 0
AX -- 功能号440DHBL -- 驱动器号: 1=A:, 2=B:, 3=C:,
...CH -- 0x08CL -- 0x6A
通过 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) // 数据缓冲区指针
在写方式下,SI寄存器的位0设置为1,位15-13在磁盘的不同区域需要有
不同的值:
{ 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(®s, 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 ®s, sizeof(regs),
//
输出数据缓冲区与长度 ®s, 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(®s, 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 ®s, sizeof(regs),
//
输出数据缓冲区与长度 ®s, 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(®s, 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 ®s,
sizeof(regs), // 输入数据缓冲区与长度
®s,
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(®s, 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 ®s, sizeof(regs),
//
输入数据缓冲区与长度 ®s, 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);
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 外,第二重要的函数,这个函数又大又复杂,牵涉到其他的一些机制,对自己,还是下不了这个毒手,所以就不说了,用一用也就有个大概的概念。