Chinaunix首页 | 论坛 | 博客
  • 博客访问: 354157
  • 博文数量: 97
  • 博客积分: 3996
  • 博客等级: 中校
  • 技术积分: 750
  • 用 户 组: 普通用户
  • 注册时间: 2005-05-24 22:27
文章分类

全部博文(97)

文章存档

2012年(1)

2011年(8)

2010年(5)

2008年(2)

2007年(26)

2006年(54)

2005年(1)

我的朋友

分类: WINDOWS

2006-08-30 23:06:14

PortTalk – 用于Windows NT/2000的端口驱动程序

(PortTalk - A Windows NT I/O Port Device Driver) 


使用Windows NTWindows 2000的烦恼之一是它们对I/O口的严格控制。它们与Windows 95 & 98有些不同, 如果你没有足够的权限读写端口的话,Windows NT/2000将产生一个exception privileged instruction错误,不仅WINDOWS NT如此,任何一款386或更高档次的处理器在保护模式下运行时都会产生类似的错误。

 

在保护模式下存取I/O口受两个条件限制, 一个是EFLAGS 寄存器中的I/O privilege level (IOPL), 另一个是Task State Segment (TSS)任务状态段中的I/O允许位图设置。

 

WINDOWS NT环境下, 只有level 0 & level 3两种I/O权限. User方式 程序运行在level 3, 设备驱动程序及WINDOWS NT内核(kernel)程序运行在level 0, 一般也称之为ring 0. 这样允许操作系统及驱动程序高可靠性的程序运行在KERNEL方式读取端口, 而不让可靠性低的user方式的程序接触I/O端口,从而防止产生冲突. 所有的user方式的程序必须经过设备驱动程序裁决是否能够存取.

 

I/O允许位图设置可以让不具备足够权限的程序(USER方式的程序)存取I/O端口. 当一条指令被执行时, 处理器首先检查这个任务是否具备存取端口的权限. 如果具备权限, 就允许它操作端口. 如果任务不具备存取I/O的权限, 处理器则检查I/O允许位图设置.

 

I/O允许位图设置是利用一个位代表每个I/O地址. 如果一个对应某端口的位被设置, 则指令会产生一个exception错误, 当然了, 如果这个位被清除就会避免错误产生. 这提供了一种让程序存取特定端口的能力. 通常一个任务只有一个I/O位图设置.

 

Accessing I/O Ports under NT/2000 NT/2000下存取端口


有两种方法解决I/O存取的问题,一是写一个运行在ring0级的设备驱动程序, 这样你就可以在USER方式下通过IOCTL调用存取你的I/O端口. 这种方法的问题在于你有要运行程序的原程序并且能够修改它.

 

另一种可替换的方法是修改I/O允许位图设置,允许一个特定的任务存取特定的I/O端口. 这允许USER方式的程序在ring3级按照I/O允许位图设置, 不受限制地访问I/O端口. 这不是一种最佳的方法(写一个设备驱动程序应该是最佳方法), 但它够让你现有的程序运行在windows NT/2000, PortTalk会降低程序的运行效率. 每次通过IOCTL调用进行向端口读写一个字节或一个字,处理器必须通ring3下切换到ring0下工作, 然后再切换到ring3. PortTalk设备驱动程序含有全部源代码. 它允许使用者修改I/O允许位图设置并通过IOCTL调用读写端口.

 

兼容性Windows NT/2000下使用已有的程序


PortTalk可以和AllowIO一起使用,让已有程序在Windows NT/2000下存取端口数据. 正如你所知道的那样, 任何一个32位的程序都会造成一个Privileged Instruction Exception错误, 许多辛苦的编程人员在Windows 9598下写好了DLL动态链接库. 如果你想在Windows NT下直接使用它的话,一定会产生错误. PortTalk就可以消除出现的错误.

 

16位的WINDOWSDOS程序可以在虚拟机上运行. 在许多情况下, 已有的程序在Windows NT/2000可以运行, 但有些程序则不然. 虚拟机支持通讯端口, 视频, 鼠标和键盘. 因此,任何一个使用这些端口的设备都应该能够正常运行, 然而, 经常会在时序上出现问题. 其它MS-DOS程序存取特定的硬件时需要VDDs (Virtual Device Drivers) 支持才能在Windows NT下运行.

 

虚拟机一般是截取I/O操作并将它发送到I/O处理程序. 它处理这种I/O的方式是限制I/O操作的权限并生成一个例外的处理程序从STACK中找到最后的指令并进行译码. 如果给定VDM足够的I/O操作权限, 就不用截取I/O操作了, 这样就减少出问题的几率及对VDDs的依赖.

 

为了改变进程的IOPM, 我们要首先获得进程的ID. 这种进程通常是我们人为的运行的,所以我们可以向我们的设备驱动程序发送ProcessID. 一个小程序可以用来接收程序的名字并作为一个参数. 这个程序然后产生进程(运行程序), 这样便在系统中开始了一个新的进程.

 

: 我们还可以用操作系统注册一个回调,让它通知我们的驱动程序哪个进程开始了,它的ID是什么. 我们然后可以保持一个需要存取特定端口的进程目录. 当这个进程被执行时, 回调通知驱动程序, 它已经开始运行了,并告诉它进程的ID是什么. 然后我们就可以自动改它这个进行的IOPM. 参见进程监测驱动程序Process.zip

 

当一个Windows 32位的程序用CreateProcess()开始时, 它将返回这个32位程序的ProcessID. 这个ID会通过IOCTL调用被送至设备驱动程序.

 

DOS程序没有它们自己的ProcessID. 它们在Windows NT的虚拟机下运行(NTVDM.EXE), 保护仿真MS-DOS子系统. 当一个DOS程序用这个程序调用时, 它将为NTVDM.EXE获取ProcessID从而改变了NTVDMIOPM.

 

然而,如果NTVDM 已经驻留了 (如果另一个DOS程序在运行的话) 它将返回一个为零值的processID. 如果NT虚拟DOS机器的IOPM已经被设置为可以进行端口操作的话,这不会有什么问题, 但如果第一个DOS程序是通过命令行方式调用的而未使用AllowIO的话, NTVDM 将无法获得修改了的IOPM.

 

Windows 3.1程序通过WOW (Windows on Win32)运行. 这是另一种在NTVDM下运行的保护方式的子系统. 运行一个Win3.1程序将返回NTVDM下的ProcessID.

 

当驱动程序有了ProcessID, 它找到我们新运行程序的进程指针并设定IOPM并允许所有的I/O操作. 一旦ProcessID送给了PortTalk 设备驱动程序, AllowIO程序就结束了.

 

NTVDM.EXE下运行DOS/Win 3.1程序一般情况下不会有什么问题. NTVDM 将截取大多数IO调用并检查注册项, 确保这些端口未被使用. 如果它们被使用, 则会出现类似的错误信息, 让用户选择结束程序或忽略错误. 如果用户选择了忽略错误, 用户将无法存取I/O端口.

 

然而, PortTalk则可以去掉对I/O口的保护, 让应用程序可以访问任何端口. 鼠标口是计算机中已经有的端口, 如果你通过PortTalk访问这个端口, 鼠标就不动了. 因此, 使用PortTalk程序需要有一定的端口知识, 不能乱用, 否则系统就会现问题.  解决这个问题的一个方法是有选择地开放特定的口, 不打开其它的口.

 

C:\>allowio Test.exe /a        让应用程序访问任何一个端口

C:\>allowio Test.exe 0x378    应用程序只能访问0x378 to 0x37F

 

因为一个Byte表示8个端口地址, 大多数设备使用8个或16个地址段, 你不用把每一个端口都在程序后面指明, 只指定8字节边界上的基地址就行了. 基于这种方法, 程序后面的0x378可以让应用程序访问全部LPT1的端口, 含数据端口,状态端口及控制寄存器.

 

操作IOPM (I/O Permission Bitmap)


Kernel方式驱动程序中改变IOPM需要两个书中未公开的调用, Ke386IoSetAccessProcess Ke386SetIoAccessMap PsLookupProcessByProcessId.

 

PsLookupProcessByProcessId(IN ULONG ulProcId,OUT struct _EPROCESS ** pEProcess);

 

IOPM例程使用一个指针而不是ProcessID. 因此, 我们第一件要做的事情是找到ProcessID程序的Pointer. 有些例程是书中写明的, PsGetCurrentProcess(),  但我们要得到不是当前的进程, 而是指向我们允许的ProcessID的例程. 这个信息以ProcessID的形式被发送到驱动程序. 我们必须用书中未写明的PsLookupProcessByProcessId将我们的ProcessID换算出一个指针.

 

有了指针以后, 我们就可以操作I/O允许位图的设置了. 使用的调用例程是:

 

       void Ke386SetIoAccessMap(int, IOPM *);

       void Ke386QueryIoAccessMap(int, IOPM *);

       void Ke386IoSetAccessProcess(PEPROCESS, int);

Ke386SetIoAccessMap考贝指定的IOPMTSS. Ke386QueryIoAccessMap负责从TSS中读出. IOPM是一个8192字节的数组, 指明了哪个端口是被允许的, 哪些端口是被禁止的. 每个地址用一个二进制位表示, 8192个字节则可达到64K的存取范围. 某一位的值是零则表示允许, 如果某一位的值是1则表示被禁止.

 

IOPM被考入至TSS, IOPM偏移指针必须调整到我们的IOPM. Ke386IoSetAccessProcess可以实现. Int参数必须设定为1才能让它进入可设定状态. 无参数的调用将取消得到的Pointer.

 

与设备驱动程序交换信息 用户方式APIs(User Mode APIs)


PortTalk还有IOCTLs功能, 通过它也可以读写端口. 在这种情况下, 你的用户方式的程序将打开PortTalk设备驱动程序, 将数据通过IOCTL传送过去. 然后驱动程序与I/O口在ring级下进行对话.

 

Porttalk提供了一个READ_PORT_UCHAR IOCTL0x08 WRITE_PORT_UCHARIOCTL 0x0C. 详情参见PortTalk Device Driver 原程序.

 

下载Download PortTalk


Download From BeyondLogic  25KB

Or

Download From daqChina  25KB

 

本文档源于 Beyond Logic 站点

编译整理:

中海油田服务有限公司研发中心
北京
232信箱 101149
信息技术及产品制造部
  宋永强

转载时请保留文档来源并通知 或在站点留言薄留言声明. 谢谢合作!

 

阅读(2138) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~