分类: WINDOWS
2006-08-30 23:06:14
PortTalk – 用于Windows NT/2000的端口驱动程序
(PortTalk - A Windows NT I/O Port Device Driver)
使用Windows NT及Windows 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 95及98下写好了DLL动态链接库. 如果你想在Windows NT下直接使用它的话,一定会产生错误. 用PortTalk就可以消除出现的错误.
16位的WINDOWS和DOS程序可以在虚拟机上运行. 在许多情况下, 已有的程序在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从而改变了NTVDM的IOPM.
然而,如果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考贝指定的IOPM到TSS. 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_UCHAR和IOCTL 0x0C. 详情参见PortTalk Device Driver 原程序.
下载Download PortTalk
Download From BeyondLogic 25KB
Or
Download From daqChina 25KB
本文档源于 Beyond Logic 站点
编译整理:
中海油田服务有限公司研发中心
北京232信箱 101149
信息技术及产品制造部 宋永强
转载时请保留文档来源并通知 或在站点留言薄留言声明. 谢谢合作!