首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 組合語言

DeviceIoControl 實戰系列(六):核心模式驅動實現用戶態物理端口讀寫 (CMOS/喇叭)

     編輯:組合語言

Windows NT 系列 (NT/2000/XP/後續版本) 採用核心/用戶態分離架構,應用程式 (Ring 3) 無法直接執行 IN/OUT 特權指令存取硬體端口。本文完整示範:編寫 WDM 核心驅動 MyPort.sys、動態安裝/啟動服務、用戶態 DLL 封裝 DeviceIoControl 呼叫,實現 CMOS 讀取與 PC Speaker 控制。

架構總覽

用戶態應用 → ReadPortByte/WritePortByte (DLL)
    → DeviceIoControl(hDevice, IOCTL, ...)
    → 核心驅動 MyPort.sys (Ring 0)
        → _inp/_outp (編譯器內建) / 直接 IN/OUT 指令
        → 硬體端口 (0x70/0x71 CMOS, 0x42/0x43/0x61 Speaker)

核心驅動 MyPort.sys 關鍵實作

1. IOCTL 定義


#define FILE_DEVICE_MYPORT 0x0000F000
#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)

2. IOPM (I/O Permission Map) 設定 — 核心關鍵

  • 每進程一個 IOPM (8192 bytes = 65536 bits),對應 0x0000-0xFFFF 端口。
  • Bit=0 允許存取,Bit=1 禁止。驅動載入時分配非分頁記憶體 MmAllocateNonCachedMemory,全清零 (允許所有端口)。
  • 關鍵未文件化核心服務:

void Ke386SetIoAccessMap(int MapNum, IOPM *pIOPM);      // 載入 IOPM 到系統表
void Ke386IoSetAccessProcess(PEPROCESS Process, int MapNum); // 將進程關聯到 IOPM

在 DriverEntry 中對當前進程啟用:


Ke386IoSetAccessProcess(PsGetCurrentProcess(), 1);
Ke386SetIoAccessMap(1, pIOPM);

3. IRP_MJ_DEVICE_CONTROL 處理


switch (IrpStack->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_MYPORT_READ_BYTE:
    pvIOBuffer[1] = _inp((USHORT)pvIOBuffer[0]); // 輸入緩衝[0]=端口, 輸出緩衝[1]=資料
    Irp->IoStatus.Information = 8; break;
case IOCTL_MYPORT_WRITE_BYTE:
    _outp((USHORT)pvIOBuffer[0], (UCHAR)pvIOBuffer[1]); break;
}

緩衝模式 METHOD_BUFFERED:輸入/輸出共用 SystemBuffer,簡單安全。

用戶態動態載入驅動


BOOL StartDriver(LPCTSTR lpszDriverPath, LPCTSTR lpszServiceName) {
    SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    SC_HANDLE hSvc = CreateService(hSCM, lpszServiceName, lpszServiceName,
        SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START,
        SERVICE_ERROR_NORMAL, lpszDriverPath, NULL, NULL, NULL, NULL, NULL);
    if (!hSvc && GetLastError()==ERROR_SERVICE_EXISTS)
        hSvc = OpenService(hSCM, lpszServiceName, SERVICE_ALL_ACCESS);
    StartService(hSvc, 0, NULL);
    CloseServiceHandle(hSvc); CloseServiceHandle(hSCM);
    return TRUE;
}

對應 StopDriver 執行 ControlService(SERVICE_CONTROL_STOP) + DeleteService。

用戶態封裝 API


HANDLE hMyPort;
BOOL OpenMyPort() {
    StartDriver(_T("system32\\drivers\\MyPort.sys"), _T("MyPort"));
    hMyPort = CreateFile(_T("\\\\.\\MyPort"), GENERIC_READ|GENERIC_WRITE,
        FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    return hMyPort != INVALID_HANDLE_VALUE;
}
BYTE ReadPortByte(WORD port) {
    DWORD buf[2] = {port, 0}, dwOut;
    DeviceIoControl(hMyPort, IOCTL_MYPORT_READ_BYTE, buf, 8, buf, 8, &dwOut, NULL);
    return (BYTE)buf[1];
}
void WritePortByte(WORD port, BYTE data) {
    DWORD buf[2] = {port, data}, dwOut;
    DeviceIoControl(hMyPort, IOCTL_MYPORT_WRITE_BYTE, buf, 8, buf, 8, &dwOut, NULL);
}

上層應用:CMOS 讀取與 Speaker 播放


BYTE ReadCmos(BYTE index) {
    WritePortByte(0x70, index);  // 索引埠 (唯寫)
    return ReadPortByte(0x71);   // 資料埠
}

void Sound(DWORD freq) {         // 8253/8254 Timer Channel 2 + Speaker
    if (freq < 20 || freq > 20000) return;
    freq = 1193181 / freq;
    BYTE b = ReadPortByte(0x61);
    if ((b & 3) == 0) { WritePortByte(0x61, b | 3); WritePortByte(0x43, 0xB6); }
    WritePortByte(0x42, (BYTE)(freq & 0xFF));
    WritePortByte(0x42, (BYTE)(freq >> 8));
}
void NoSound() {
    BYTE b = ReadPortByte(0x61);
    WritePortByte(0x61, b & 0xFC);
}

測試:迴圈讀取 CMOS 128 bytes;播放 Do-Re-Mi (262/288/320 Hz)。

總結與替代方案

完整流程:驅動編譯 → 服務安裝 → 設備開啟 → IOCTL → 端口存取。若覺得繁瑣,可將上層封裝為 MyPort.dll (DRV/DLL/APP 三層分離),應用僅呼叫 ReadCmos/Sound 等高階 API。參考 Yariv Kaplan WinIO 專案獲取更成熟的實作 (含實體記憶體存取)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved