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

http://tech.ddvip.com   2008年09月08日    社区交流

本文详细介绍实战 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);
}

来源:blog.csdn    作者:晚起的虫子    责编:豆豆技术应用

正在加载评论...