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

本文介绍在Windows NT/2000/XP中直接访问物理端口的方法,包括编写驱动程序实现I/O读写操作,以及在Windows 9X中通过DeviceIoControl调用INT 21H 7305H与440DH功能实现磁盘直接访问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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


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

A
看似小小问题,难倒多少好汉!
 
NT/2000/XP
从安全性、可靠性、稳定性上考虑,应用程序和操作系统是分开的,操作系统代码运行在核心

态,有权访问系统数据和硬件,能执行特权指令;应用程序运行在用户态,能够使用的接口和访问系统数
据的权限都受到严格限制。当用户程序调用系统服务时,处理器捕获该调用,然后把调用的线程切换到核
心态。当系统服务完成后,操作系统将线程描述表切换回用户态,调用者继续运行。 
想在用户态应用程序中实现 I/O 读写,直接存取硬件,可以通过编写驱动程序,实现 CreateFile

CloseHandle
DeviceIOControlReadFileWriteFile 等功能。从 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"
//Device//MyPort";
const WCHAR DOSNameBuffer[] = L"
//DosDevices//MyPort";
  
//
这是两个在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"system32/drivers"目录中

 
    bResult = StartDriver("system32//drivers//MyPort.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功能来完成。该调用支持FAT12FAT16FAT32,适用于
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标志。
 
通过IOCTLVWIN32_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     //
确定DeviceIoControlINT21 都无错误
     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 
//Driver//Kbdclass
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 
//Driver//Kbdclass
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 
//Driver//i8042prt
Args: 00000000 00000004 000b0008 00000000
[ e, 0] 0 0 fe4f5df0 fe426688 00000000-00000000 
//Driver//Kbdclass
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 1CurrentStackLocation 指向前一个(地址更小处)
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 外,第二

重要的函数,这个函数又大又复杂,牵涉到其他的一些机制,对自己,还是下不了这个毒手,所以就不说
了,用一用也就有个大概的概念。 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值