65.9K
CodeProject 正在变化。 阅读更多。
Home

Kport 第 2 部分:在 Win NT/2000/XP 下直接访问 I/O 端口

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.52/5 (13投票s)

2005年5月2日

CPOL

7分钟阅读

viewsIcon

101156

downloadIcon

7422

现在,VC++ 6 中包含完整的代码并有更多解释。

Sample screenshot

引言

这是文章 kport.dll 的第二部分,用于在 Windows NT/2K/XP 中直接访问 I/O 端口。

我写了一个新的示例,用于在 VC++ 6 中使用此 DLL。

背景

请记住,要编译 kioport.sys,您可能需要安装 DDK。

Kioport.sys

这篇文章来自 Beyondlogic Portalk

在 NT/2000/XP 下访问 I/O 端口

有两种解决方案可以解决 Windows NT/2000/XP 下的 I/O 访问问题。第一种解决方案是编写一个在 ring 0(I/O 权限级别 0)下运行的设备驱动程序,以代表您访问 I/O 端口。数据可以通过 IOCTL 调用在您的用户模式程序和设备驱动程序之间传递。驱动程序然后可以执行您的 I/O 指令。这样做的问题是,它假定您拥有源代码来进行此类更改。

另一种可能的替代方法是修改 I/O 权限位图,允许特定任务访问某些 I/O 端口。这使得您的用户模式程序在 ring 3 中运行,可以根据 I/O 权限位图对选定的端口执行无限制的 I/O 操作。此方法并不真正推荐,但它为允许现有应用程序在 Windows NT/2000/XP 下运行提供了一种方式。编写设备驱动程序来支持您的硬件是首选方法。设备驱动程序应在访问端口之前检查任何冲突。

但是,使用 Kioport 这样的驱动程序可能会变得相当低效。每次发出 IOCTL 调用以读写端口的一个字节或字时,处理器必须从 ring 3 切换到 ring 0 来执行操作,然后切换回来。

兼容性 - 在 Windows NT/2000/XP 下使用现有应用程序

如您所知,任何 32 位程序都会导致特权指令异常。由于如今大多数编程语言都不提供读取和写入端口的函数,因此为 Windows 9x 和 ME 制作了许多“技巧”,例如 .DLL 库。但是,如果您需要在 Windows NT 下运行此类程序,将会发生异常。

16 位 Windows 和 DOS 程序将在虚拟机上运行。在许多情况下,现有应用程序在 Windows NT/2000/XP 下应该是透明的。然而,有些就是无法运行。虚拟机支持通信端口、视频、鼠标和键盘。因此,任何使用这些常见 I/O 端口的程序都应该可以运行,但经常会出现时序问题。访问特定硬件的其他 MS-DOS 程序需要 VDD(虚拟设备驱动程序)来使它们能够与 Windows NT 一起使用。

虚拟机将拦截 I/O 操作并将其发送给 I/O 处理程序进行处理。虚拟机执行此操作的方式是,赋予 I/O 操作不足的权限,并创建一个异常处理程序来回溯堆栈,查找最后一个指令并对其进行解码。通过赋予 VDM 对 I/O 端口的完全权限,它就无法拦截 I/O 操作,从而减少了时序问题或提供 VDD 来处理更晦涩的硬件的需求。

为了更改进程的 IOPM,我们必须首先拥有要授予访问权限的进程的进程 ID。这是通过创建我们自己的进程来实现的,以便我们可以将 ProcessID 传递给我们的设备驱动程序。使用一个接受程序名作为参数的小型应用程序。该应用程序然后创建进程(即执行程序),该程序将作为系统中的另一个进程启动和继续。

注意:我们还可以向操作系统注册一个回调,该回调会通知我们的驱动程序任何启动的进程及其 ID。然后,我们可以维护一个我们希望拥有对某些端口的访问权限的进程目录。当执行此进程时,回调会通知驱动程序它已启动及其 processID。然后,我们可以自动更改该进程的 IOPM。

当使用 CreateProcess() 启动 Windows 32 位程序时,它将返回 32 位程序的 ProcessID。这通过 IOCTL 调用传递给设备驱动程序。

DOS 程序没有自己的 ProcessID。它们运行在 Windows NT 虚拟 DOS 机 (NTVDM.EXE) 下,这是一个模拟 MS-DOS 的受保护环境子系统。当使用此程序调用 DOS 程序时,它将获得 NTVDM.EXEProcessID,并因此更改 NTVDM 的 IOPM。但是,如果 NTVDM 已经驻留(如果另一个 DOS 程序正在运行),它将返回进程 ID 零。如果 NT 虚拟 DOS 机的 IOPM 已设置为允许任何 IO 操作,则这不会造成问题,但如果第一个 DOS 程序是从命令行调用的,而没有使用 "AllowIo",则 NTVDM 将没有修改的 IOPM。Windows 3.1 程序将使用 WOW(Windows on Win32)运行。这是另一个在 NTVDM 进程内运行的受保护子系统。运行 Win3.1 程序将返回 NTVDM 的 ProcessID,符合上述问题。

当设备驱动程序获得 ProcessID 后,它会找到我们新创建程序的进程指针,并将 IOPM 设置为允许所有 I/O 指令。一旦 ProcessID 被提供给我们的 PortTalk 设备驱动程序,allowio 程序就会完成。正常运行 DOS/Win 3.1 程序在 NTVDM.EXE 下不应造成任何重大问题。NTVDM 通常会拦截大多数 IO 调用,并根据注册表检查这些资源,以确保它们未被使用。如果它们正在使用,则会出现一个消息框,让用户选择终止程序或忽略错误。如果用户选择忽略错误,将不会授予对冲突的 I/O 端口的访问权限。

操纵 IOPM(I/O 权限位图)

在内核模式驱动程序中更改 IOPM 需要了解一些未公开的调用。它们是 Ke386IoSetAccessProcessKe386SetIoAccessMapPsLookupProcessByProcessId

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

IOPM 例程使用进程指针而不是 ProcessID。因此,我们的第一个任务是将 ProcessID 转换为进程指针。有一些公开的调用,如 PsGetCurrentProcess(),但我们不想要当前进程,而是我们希望授予访问权限的进程指针。此信息以 processID 的形式传递给驱动程序。然后,我们必须使用未公开的调用 LookupProcessByProcessId 将我们的 ProcessID 转换为进程指针。一旦我们有了进程指针,我们就可以使用以下未公开的调用开始操纵 I/O 权限位图。

void Ke386SetIoAccessMap(int, IOPM *);
void Ke386QueryIoAccessMap(int, IOPM *);
void Ke386IoSetAccessProcess(PEPROCESS, int);

Ke386SetIoAccessMap 将指定的 IOPM 复制到 TSS。Ke386QueryIoAccessMap 将从 TSS 读取它。IOPM 是一个 8192 字节的数组,指定哪些端口允许访问,哪些不允许。每个地址由一个位表示,因此 8192 字节将指定高达 64K 的访问。任何零位都允许访问,而一位则禁止访问。将 IOPM 复制到 TSS 后,必须调整 IOPM 偏移指针以指向我们的 IOPM。这是使用 Ke386IoSetAccessProcess 完成的。int 参数必须设置为 1 才能设置它。使用零调用该函数将删除指针。

与设备驱动程序通信 - 用户模式 API

Kioport 还具有 IOCTL,允许读取和写入 I/O 端口。在这种情况下,您的用户模式程序将打开 Kioport 设备驱动程序,并通过 IOCTL 调用将数据传递给驱动程序。然后,驱动程序在 ring 0 中与 I/O 端口通信。

Kport.Dll

这是我的 DLL,部分代码来自 Portalk。

Kport 在加载我们的应用程序时调用 OpenPort(),在卸载时调用 ClosePort(),这样我们就无需加载驱动程序。

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
                OpenPort();
                break;
     //.... case DLL_PROCESS_DETACH:
                 ClosePort();
                 break;
    }
  return TRUE;
}

OpenPort() 首先验证它是否已将驱动程序安装到 system32\driver。如果无法打开它,则尝试安装并启动它。过程如图所示。

Sample screenshot

这两个函数在 kioport.sys 中进行读写。

// Write a value to specific ports.
KPORT_API void APIENTRY Outportb(WORD PortNum, BYTE byte)
{
   UINT error;
   DWORD BytesReturned;
   BYTE Buffer[3];
   WORD * pBuffer;
   pBuffer = (WORD *)&Buffer[0];
   *pBuffer = PortNum;
   Buffer[2] = byte;
   LPSTR szt = NULL;
   error = DeviceIoControl(hKport, IOCTL_WRITE_PORT_UCHAR,
        &Buffer, 3, NULL, 
        0, &BytesReturned,
        NULL);
   if (!error)
   {
      wsprintf(szt,"Error occured during outportb while" 
             " talking to KioPort driver %d\n",GetLastError());
      MessageBox(NULL,szt,"Kport-DLL",MB_ICONINFORMATION);
   }
}
// Returns a value from specific ports.
KPORT_API BYTE APIENTRY Inportb(WORD PortNum)
{
   UINT error;
   DWORD BytesReturned;
   BYTE Buffer[3];
   WORD *pBuffer;
   pBuffer = (WORD*)&Buffer;
   *pBuffer = PortNum;
   LPSTR szt = NULL; error = DeviceIoControl(hKport,
      IOCTL_READ_PORT_UCHAR,
      &Buffer,
      2,&Buffer,
      1,&BytesReturned,
      NULL); 
   if (!error)
   {
       wsprintf(szt,"Error occured during inportb while" 
                " talking to KioPort driver %d\n", GetLastError());
       MessageBox(NULL,szt,"Kport-DLL",MB_ICONINFORMATION);
   } 
   return(Buffer[0]);
}

我现在添加了 IrpMan、Microsoft® Windows Server 2003 DDK,其中包含有关制作驱动程序的大量信息。

最后,对我的糟糕英语表示歉意。我正在学习英语的第二年,所以我不太会写这个语言。

历史

  • 2005年4月3日

    First version.

  • 2005年4月30日

    第二版。

© . All rights reserved.