WinIo内存访问驱动编译方法与源码解析
安装 Visual Studio 专业版 2022
WDK 包已经不支持
2019,请更新到2022版本搜索
最新全选即
可先下载源码便于 验证安装是否成功:WinIoNote: https://github.com/foryouos/WinIoNote

安装 SDK
查看
系统已经安装的SDK, 也可以在 上一步安装过程中安装 SDK,就不用再单独下载安装。注: 检查
系统本身所存在的 环境,版本 过多,和WDK 版本不匹配会导致 WDK 安装进去 Visual Studio报错版本不匹配.下载链接: https://learn.microsoft.com/zh-cn/windows-hardware/drivers/download-the-wdk


安装 WDK
如果在 Visual Studio 中找不到驱动程序项目模板,则表示
WDK Visual Studio扩展未正确安装。 要解决此问题,请从以下位置运行WDK.vsix文件:C:\Program Files (x86)\Windows Kits\10\Vsix\VS2022\10.0.26100.1\WDK.vsix。

如果上述方式
安装失败,可以通过,在插件里直接进行安装。如果还是失败? 查看下面的该扩展的版本比 Visual Studio 要求的版本低

WDK的版本要和自身的 系统对应,避免安装之后 出现版本不兼容,失败的问题经过测试:我使用 win11 23H2 直接安装的最新版本。

以前的 WDK 版本和其他下载 – Windows drivers | Microsoft Learn[1]
确保版本一致

激活
Vs2022 激活码:
Pro: > TD244-P4NB7-YQ6XK-Y8MMM-YWV2JEnterprise: > VHF9H-NXBBB-638P6-6JHCY-88JWH
初始化运行问题
未找到 ntddk.h

C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\km\ntddk.h确认您的
windows kits目录,加入到C++的附加项即可

严重性 代码 说明 项目 文件 行 禁止显示状态 详细信息错误 C1083 无法打开包括文件: “ntddk.h”: No such file or directory
创建 Hello Word 驱动
打开 Microsoft Visual Studio。 在文件菜单上,选择新建>项目。

生成的
解决方案

在
Source Files文件夹中创建Driver.c文件编写函数
#include<ntddk.h> //包含所有驱动程序的核心Widnows内核定义,#include<wdf.h> // 基于Windows 驱动程序框架WDF的驱动程序的定义DRIVER_INITIALIZE DriverEntry;EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;// 驱动程序的入口函数NTSTATUSDriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath){// NTSTATUS variable to record success or failure NTSTATUS status = STATUS_SUCCESS;// Allocate the driver configuration object WDF_DRIVER_CONFIG config;// Print "Hello World" for DriverEntry KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DriverEntry\n"));// Initialize the driver configuration object to register the// entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd WDF_DRIVER_CONFIG_INIT(&config, KmdfHelloWorldEvtDeviceAdd );// Finally, create the driver object status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE );return status;}// 系统检查到你的设备已到达时,会调用 EvtDeviceAdd 任务时初始化该设备的结构与资源NTSTATUSKmdfHelloWorldEvtDeviceAdd( _In_ WDFDRIVER Driver, _Inout_ PWDFDEVICE_INIT DeviceInit){// We're not using the driver object,// so we need to mark it as unreferenced UNREFERENCED_PARAMETER(Driver); NTSTATUS status;// Allocate the device object WDFDEVICE hDevice;// Print "Hello World" KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd\n"));// Create the device object status = WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &hDevice );return status;}
点击生成之后,就可以查看自己生成的
sys驱动文件。

调用测试驱动
生成的
驱动文件
HelloDriver.sys– 内核模式驱动程序文件HelloDriver.inf– 在安装驱动程序时 Windows 使用的信息文件HelloDriver.cat– 安装程序验证驱动程序的测试签名所使用的目录文件

以
管理员身份运行DriverMonitor



由于
sys文件,目前系统 都有签名认证要求,对于没有签名认证的,都会默认被Windows系统拦截。
调试方案
启动系统测试模式 cmd 中以管理员身份运行执行此指令 bcdedit /set testsigning on然后重启

获取打印的测试结果 使用 Dbgview工具,它会实时打印当前系统的驱动输出后续章节 进行更新。
WINIO基础流程
驱动程序是一个软件组件,它允许操作系统和设备进行通信。


WINIO函数解析
DriverEntry
主要对驱动程序进行初始化,由系统进程
System调用。
为 驱动程序的标准例程提供入口点。
NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath);
KdPrint
驱动打印 Logo信息通过 驱动检测工具来抓取Logo
KdPrint(("Entering DriverEntry"));
rtlInitUnicodeString 函数
函数初始化
Unicode字符的计数字符串
// 如果成功返回 STATUS_SUCCESSNTSYSAPI VOID RtlInitUnicodeString( [out] PUNICODE_STRING DestinationString,//指向要初始化的UNICODE_STRING结构的指针 [in, optional] __drv_aliasesMem PCWSTR SourceString //指向以NULL结尾的宽字符字符串指针,用于初始化目的字符串指向的计数字符串);RtlInitUnicodeString(&DeviceNameUnicodeString, L"\\Device\\TestIO");
IoCreateDevice
用于创建
供驱动程序使用的设备对象
NTSTATUS IoCreateDevice( [in] PDRIVER_OBJECT DriverObject, //指向调用方驱动程序对象的指针,每个驱动程序在指向DriverEntry例程的参数中接受指向其驱动程序对象的指针 [in] ULONG DeviceExtensionSize,//指向要为设备对象的设备扩展分配的驱动程序确定的字节数 [in, optional] PUNICODE_STRING DeviceName,// 指向包含以null结尾的Unicode字符串的缓冲区,该字符串为设备对象命名 [in] DEVICE_TYPE DeviceType,//指定系统定义的 FILE_DEVICE_XXX 常量之一,这些常量指示设备 (的类型,例如FILE_DEVICE_DISK或FILE_DEVICE_KEYBOARD) 或供应商为新类型的设备定义值 [in] ULONG DeviceCharacteristics,// 指定一个或多个系统定义的常量(ORed 在一起),这些常量提供有关驱动程序设备的其他信息。 [in] BOOLEAN Exclusive, //指定设备对象是否表示独占设备 [out] PDEVICE_OBJECT *DeviceObject // 指向变量的指针,该变量接受指向新创建的Device_OBJECT结构的指针);// WINIO驱动使用ntStatus = IoCreateDevice(DriverObject,0, &DeviceNameUnicodeString, FILE_DEVICE_WINIO,0, FALSE, &DeviceObject);
IoCreateSymbolicLink
IoCreateSymbolicLink例程在设备对象名称和设备的用户可见名称之间设置符号链接
// 创建成功之后 返回 STATUS_SUCCESSNTSTATUS IoCreateSymbolicLink( [in] PUNICODE_STRING SymbolicLinkName, //指向用户可见名称的缓冲Unicode字符串的指针 [in] PUNICODE_STRING DeviceName // 指向缓冲Unicode字符串的指针,该字符串时驱动程序创建的设备对象的名称);
ioDeleteDevice
ioDeleteDevice例程从系统中删除设备对象,例如:从系统中删除基础设备时
voidIoDeleteDevice( [in] PDEVICE_OBJECT DeviceObject // 指向要删除的设备对象的指针);//WINIO使用if (!NT_SUCCESS(ntStatus)){// Symbolic link creation failed- note this & then delete the// device object (it's useless if a Win32 app can't get at it). KdPrint(("ERROR: IoCreateSymbolicLink failed")); IoDeleteDevice(DeviceObject);}
IoGetCurrentIrpStackLocation
IoGetCurrentIrpStackLocation例程返回指向指定IRP中调用方I/O堆栈位置的指针
__drv_aliasesMem PIO_STACK_LOCATION IoGetCurrentIrpStackLocation( [in] PIRP Irp // 指向 IRP 的指针。);// WINIO示例// Init to default settingsIrp->IoStatus.Status = STATUS_SUCCESS;Irp->IoStatus.Information = 0;IrpStack = IoGetCurrentIrpStackLocation(Irp);// Get the pointer to the input/output buffer and it's lengthpvIOBuffer = Irp->AssociatedIrp.SystemBuffer;dwInputBufferLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength;dwOutputBufferLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
每个驱动程序都
必须使用发送的每个 IRP调用IoGetCurrentIrpStackLocation,以获取当前请求的任何参数。 除非驱动程序为驱动程序处理的每个IRP_MJ_\*XXX代码提供调度例程,否则驱动程序还必须在IRP中检查其I/O堆栈位置,以确定正在请求的操作。
WRITE_PORT_UCHAR
将字节
写入指定的端口地址
voidWRITE_PORT_UCHAR( [in] PVOID Port, // 指向端口的指针,该端口必须是I/O空间中的映射内存范围 [in] ULONG Value // 指定要写入端口的字节);//WINIO示例代码memcpy(&PortStruct, pvIOBuffer, dwInputBufferLength);switch (PortStruct.bSize){case1: WRITE_PORT_UCHAR((PUCHAR)(USHORT)PortStruct.wPortAddr, (UCHAR)PortStruct.dwPortVal);break;case2: WRITE_PORT_USHORT((PUSHORT)(USHORT)PortStruct.wPortAddr, (USHORT)PortStruct.dwPortVal);break;case4: WRITE_PORT_ULONG((PULONG)(USHORT)PortStruct.wPortAddr, PortStruct.dwPortVal);break;}
READ_PORT_UCHAR
从指定的
端口地址读取字节
UCHAR READ_PORT_UCHAR( [in] PVOID Port //指定端口地址,该地址必须是I/O空间中的映射内存范围);// WINIO示例代码switch (PortStruct.bSize){case1: PortStruct.dwPortVal = (ULONG)READ_PORT_UCHAR((PUCHAR)(USHORT)PortStruct.wPortAddr);break;case2: PortStruct.dwPortVal = (ULONG)READ_PORT_USHORT((PUSHORT)(USHORT)PortStruct.wPortAddr);break;case4: PortStruct.dwPortVal = READ_PORT_ULONG((PULONG)(USHORT)PortStruct.wPortAddr);break;}
ZwOpenSection
打开现有节对象的[2] 句柄。
NTSYSAPI NTSTATUS ZwOpenSection( [out] PHANDLE SectionHandle,// 指向 HANDLE 变量的指针,该变量接收 section 对象的句柄。 [in] ACCESS_MASK DesiredAccess,//指向一个ACCESS_MASK值,该值确定对对象的请求访问权限 [in] POBJECT_ATTRIBUTES ObjectAttributes // 该结构指定对象名称和其他属性);
HalTranslateBusAddress
This function translates a physical bus address to a physical system address.
该函数将物理总线地址转换为物理系统地址。
BOOL HalTranslateBusAddress( INTERFACE_TYPE InterfaceType, // 表示总线接口类型。支持总线类型的上限始终是MaximumInterfaceType。这指示了设备使用的总线类型,如果PCI,ISA等 ULONG BusNumber, // 设备的零基系统分配总线编号。用于在同一类型的多个总线中识别总线 PHYSICAL_ADDRESS BusAddress, //相对于总线的地址,此地址通常是设备在总线上的物理地址 PULONG AddressSpace, //输入时初始化为PULONG,输出时为端口号或内存地址,值0x0表示内存地址,0x1表示I/O空间 PPHYSICAL_ADDRESS TranslatedAddress // 指向转换后地址的指针,此参数返回的地址可以被设备驱动程序使用);
ZwMapViewOfSection
将节的视图映射到主题进程的虚拟地址空间中
NTSYSAPI NTSTATUS ZwMapViewOfSection( [in] HANDLE SectionHandle,//节对象的句柄, [in] HANDLE ProcessHandle,// 对象的句柄,该对象表示视图应映射到的进程。 [in, out] PVOID *BaseAddress, // 指向接受视图基址的变量的指针 [in] ULONG_PTR ZeroBits, // 指定视图基址中必须为0的高序地址位数 [in] SIZE_T CommitSize,// 指定视图的初始提交区域的大小(以字节为单位) [in, out, optional] PLARGE_INTEGER SectionOffset,// 指向变量的指针,该变量接收从节开头到视图的偏移量(以字节为单位) [in, out] PSIZE_T ViewSize,// 指向SIZE_T变量的指针 [in] SECTION_INHERIT InheritDisposition,// 指向如何与子进程共享视图 [in] ULONG AllocationType,// 指定一组表示,用于描述要为指定的页面区域执行的分配类型 [in] ULONG Win32Protect// 指定要应用于映射视图的页面保护);
ZwUnmapViewOfSection
从主题进程的虚拟地址空间中取消映射节的视图
NTSYSAPI NTSTATUS ZwUnmapViewOfSection( [in] HANDLE ProcessHandle, //以前传递给 ZwMapViewOfSection的进程对象的句柄 [in, optional] PVOID BaseAddress // 指向要取消映射的视图基虚拟地址的指针,可是视图中的任何虚拟地址);
此例程从
指定进程的虚拟地址空间中取消映射包含*BaseAddress* 的节的整个视图,即使*BaseAddress* 未指向视图的开头也是如此。
ObDereferenceObject
递减给定对象的引用次数并执行保留检查
ObDereferenceObject返回为系统使用保留的值。驱动程序必须将此值视为 VOID
voidObDereferenceObject( [in] a //指向对象主体的指针);
zwClose
zwClose例程关闭对象句柄wClose 是一个泛型例程,可对任何类型的对象进行操作。
NTSYSAPI NTSTATUS ZwClose( [in] HANDLE Handle //任何类型的对象的句柄);
DeviceIoControl
将控制代码直接发送到指定的设备驱动程序,相应的设备执行应用的操作
BOOL DeviceIoControl( [in] HANDLE hDevice, //设备句柄 [in] DWORD dwIoControlCode,// 操作的控制码,在winio_nt.h 文件或者 sys头文件中 [in, optional] LPVOID lpInBuffer,//指向输入缓冲区的指针,其中包含执行操作所需的数据 [in] DWORD nInBufferSize,//输入缓冲区的大小 [out, optional] LPVOID lpOutBuffer,//指向输出缓冲区的指针,用于接收操作返回的数据 [in] DWORD nOutBufferSize,// 输出缓冲区的大小 [out, optional] LPDWORD lpBytesReturned,// 指向变量的指针,改变量接受存储在输出缓冲区中的数据的大小(以字节为单位) [in, out, optional] LPOVERLAPPED lpOverlapped// 指向 OVERLAPPED 结构的指针。);
创建动态链接库项目



创建文件

smbios.h和smbios.cpp使用开源库:DumpSMBIOShttps://github.com/KunYi/DumpSMBIOS
头文件
对于 全局变量 但是 在实现函数中需要全局使用,请放到对应的 实现函数 当中
#ifndef SMBIOSAPI_H_#define SMBIOSAPI_H_#include<Windows.h>#define _DllExport _declspec(dllexport) //使用宏定义缩写下// for Type 0typedefstruct { PWCHAR m_wszBIOSVendor; PWCHAR m_wszBIOSVersion; PWCHAR m_wszBIOSReleaseDate; DWORD m_BIOSSysVersion; DWORD m_BIOSECVersion;} BIOSInfo;// for Type 1typedefstruct { PWCHAR m_wszSysManufactor; PWCHAR m_wszSysProductName; PWCHAR m_wszSysVersion; PWCHAR m_wszSysSerialNumber; UUID m_SysUUID; PWCHAR m_wszSysSKU; PWCHAR m_wszSysFamily;} SystemInfo;// for Type 2typedefstruct { PWCHAR m_wszBoardManufactor; PWCHAR m_wszBoardProductName; PWCHAR m_wszBoardVersion; PWCHAR m_wszBoardSerialNumber; PWCHAR m_wszBoardAssetTag; PWCHAR m_wszBoardLocation;} BoardInfo;// 函数 实现区域extern"C"{_DllExport voidSMBIOS_INIT(); // 初始化SMBIOS数据_DllExport voidSMBIOS_Free(); // 释放 SMBIOS 申请的 资源// 获取单个数据_DllExport DWORD SMBIOS_Get_BIOS_Version(); // 获取 BIOS信息 结构体// 获取数据结构体 指针 传入 结构体指针,返回对应的结构体 数据_DllExport voidSMBIOS_Get_BIOS_Info(BIOSInfo* info); // 获取 BIOS信息 结构体_DllExport voidSMBIOS_Get_SYSTEM_Info(SystemInfo* info); // 获取系统 信息 结构体_DllExport voidSMBIOS_Get_BOARD_Info(BoardInfo* info); // 获取主板 信息 结构体// 获取 UUID需要的 数据 包括 UUID SysSerialNumber BoardSerialNumber SysSKU// 此函数 为 产线 UUID 测试 使用 传入对应 char 字符串// 输出 对应的 字符串值_DllExport wchar_t* SMBIOS_Get_UUID_Info(constchar* UUID_Type);}#endif// SMBIOSAPI_H_
实现函数
实现函数 主要是 对 头文件中的 函数 进行实现。
#include"pch.h"// use stdafx.h in Visual Studio 2017 and earlier#include"smbiosapi.h"#include"smbios.h"#include<memory>BIOSInfo* g_bios_info = nullptr; // 全局 BIOS Info信息SystemInfo* g_system_info = nullptr; // 全局 系统信息 结构体BoardInfo* g_board_info = nullptr; // 全局 主板 信息 结构体_DllExport voidSMBIOS_INIT(){if (g_bios_info == nullptr) { g_bios_info = new BIOSInfo; }if (g_system_info == nullptr) { g_system_info = new SystemInfo; }if (g_board_info == nullptr) { g_board_info = new BoardInfo; }const SMBIOS& SmBios = SMBIOS::getInstance();// 初始化 BIOS信息 g_bios_info->m_wszBIOSVendor = SmBios.BIOSVendor(); g_bios_info->m_BIOSECVersion = SmBios.BIOSECVersion(); g_bios_info->m_BIOSSysVersion = SmBios.BIOSSysVersion(); g_bios_info->m_wszBIOSReleaseDate = SmBios.BIOSReleaseDate(); g_bios_info->m_wszBIOSVersion = SmBios.BIOSVersion();// 初始化 系统信息 g_system_info->m_wszSysManufactor = SmBios.SysManufactor(); g_system_info->m_wszSysProductName = SmBios.SysProductName(); g_system_info->m_wszSysSerialNumber = SmBios.SysSerialNumber(); g_system_info->m_wszSysFamily = SmBios.SysFamily(); g_system_info->m_SysUUID = SmBios.SysUUID(); g_system_info->m_wszSysSKU = SmBios.SysSKU(); g_system_info->m_wszSysVersion = SmBios.SysVersion();// 初始化主板信息 g_board_info->m_wszBoardAssetTag = SmBios.BoardAssetTag(); g_board_info->m_wszBoardLocation = SmBios.BoardLocation(); g_board_info->m_wszBoardManufactor = SmBios.BoardManufactor(); g_board_info->m_wszBoardProductName = SmBios.BoardProductName(); g_board_info->m_wszBoardSerialNumber = SmBios.BoardSerialNumber(); g_board_info->m_wszBoardVersion = SmBios.BoardVersion();return ;}_DllExport voidSMBIOS_Free(){if (g_bios_info != nullptr) {delete g_bios_info; g_bios_info = nullptr; }if (g_system_info == nullptr) {delete g_system_info; g_system_info = nullptr; }if (g_board_info == nullptr) {delete g_board_info; g_board_info = nullptr; }return ;}_DllExport DWORD SMBIOS_Get_BIOS_Version(){if (g_bios_info != nullptr) {return g_bios_info->m_BIOSECVersion; }return DWORD();}_DllExport voidSMBIOS_Get_BIOS_Info(BIOSInfo* info){if (info != NULL) { info->m_wszBIOSVendor = g_bios_info->m_wszBIOSVendor; info->m_BIOSECVersion = g_bios_info->m_BIOSECVersion; info->m_BIOSSysVersion = g_bios_info->m_BIOSSysVersion; info->m_wszBIOSReleaseDate = g_bios_info->m_wszBIOSReleaseDate; info->m_wszBIOSVersion = g_bios_info->m_wszBIOSVersion; }return ;}_DllExport voidSMBIOS_Get_SYSTEM_Info(SystemInfo* info){if (info != NULL) { info->m_wszSysManufactor = g_system_info->m_wszSysManufactor; info->m_wszSysProductName = g_system_info->m_wszSysProductName; info->m_wszSysVersion = g_system_info->m_wszSysVersion; info->m_wszSysSerialNumber = g_system_info->m_wszSysSerialNumber; info->m_SysUUID = g_system_info->m_SysUUID; info->m_wszSysSKU = g_system_info->m_wszSysSKU; info->m_wszSysFamily = g_system_info->m_wszSysFamily; }return ;}_DllExport voidSMBIOS_Get_BOARD_Info(BoardInfo* info){if (info != NULL) { info->m_wszBoardManufactor = g_board_info->m_wszBoardManufactor; info->m_wszBoardProductName = g_board_info->m_wszBoardProductName; info->m_wszBoardVersion = g_board_info->m_wszBoardVersion; info->m_wszBoardSerialNumber = g_board_info->m_wszBoardSerialNumber; info->m_wszBoardAssetTag = g_board_info->m_wszBoardAssetTag; info->m_wszBoardLocation = g_board_info->m_wszBoardLocation; }return ;}wchar_t* UUIDToString(const UUID& uuid){wchar_t* buffer = newwchar_t[37]; // 动态分配内存,长度为37 swprintf(buffer, 37,L"%08x-%04x-%04x-%04x-%012llx", uuid.Data1, uuid.Data2, uuid.Data3, (uuid.Data4[0] << 8) | uuid.Data4[1], *reinterpret_cast<constunsignedlonglong*>(uuid.Data4 + 2));return buffer; // 返回指向动态分配内存的指针}// 传入的 参数 UUID SysSerialNumber BoardSerialNumber SysSKU_DllExport wchar_t* SMBIOS_Get_UUID_Info(constchar* UUID_Type){if (strcmp(UUID_Type, "UUID") == 0) {return UUIDToString(g_system_info->m_SysUUID); }elseif (strcmp(UUID_Type, "SysSerialNumber") == 0) {return g_system_info->m_wszSysSerialNumber; }elseif (strcmp(UUID_Type, "BoardSerialNumber") == 0) {return g_board_info->m_wszBoardSerialNumber; }elseif (strcmp(UUID_Type, "SysSKU") == 0) {return g_system_info->m_wszSysSKU; }returnnullptr; // 如果 UUID_Type 不匹配,返回 nullptr}
Visual Studio 静态链接配置
配置
lib静态链接库所在目录

链接对应的
动态链接库名称

// TestSMBIOS.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include<iostream>#include<Windows.h>#include"../SMBIOSDLL/smbiosapi.h"usingnamespacestd;// #pragma comment(lib,"SMBIOSDLL.lib")intmain(){cout << "Hello World!\n"; SMBIOS_INIT(); DWORD BIOS_Version = SMBIOS_Get_BIOS_Version();cout << "BIOS版本:" << BIOS_Version <<endl ;// 获取 BIOS信息 BIOSInfo* m_bios_info = new BIOSInfo; SMBIOS_Get_BIOS_Info(m_bios_info); wprintf(L"BIOS Version: %s\r\n", m_bios_info->m_wszBIOSReleaseDate);// 获取 系统 信息 SystemInfo* m_system_info = new SystemInfo; SMBIOS_Get_SYSTEM_Info(m_system_info); wprintf(L"Sys Manufactor: %s\r\n", m_system_info->m_wszSysManufactor);// 获取 主板信息 BoardInfo* m_board_info = new BoardInfo; SMBIOS_Get_BOARD_Info(m_board_info); wprintf(L"BIOS ProductName: %s\r\n", m_board_info->m_wszBoardProductName); SMBIOS_Free();return0;}
Qt 连接静态链接库
右键项目名称–> 添加库 –> 选择 外部库 —> 选择库文件


pro 文件静态 引用 头文件
win32: LIBS += -L$$PWD/./ -lSMBIOSDLLINCLUDEPATH += $$PWD/''DEPENDPATH += $$PWD/''win32:!win32-g++: PRE_TARGETDEPS += $$PWD/./SMBIOSDLL.libelse:win32-g++: PRE_TARGETDEPS += $$PWD/./libSMBIOSDLL.a
#include"smbiosapi.h"// 将对应的头文件 引入到项目 当中SMBIOS_INIT();qDebug()<<"BIOS版本信息:"<<SMBIOS_Get_BIOS_Version();// 获取 BIOS信息BIOSInfo* m_bios_info = new BIOSInfo;SMBIOS_Get_BIOS_Info(m_bios_info);qDebug()<<m_bios_info->m_BIOSSysVersion;// 获取 系统 信息SystemInfo* m_system_info = new SystemInfo;SMBIOS_Get_SYSTEM_Info(m_system_info);qDebug()<<QString::fromWCharArray(m_system_info->m_wszSysManufactor);// 获取 主板信息BoardInfo* m_board_info = new BoardInfo;SMBIOS_Get_BOARD_Info(m_board_info);qDebug()<<QString::fromWCharArray(m_board_info->m_wszBoardProductName);// 测试 UUID 模块 返回的 字符qDebug()<< QString::fromWCharArray(SMBIOS_Get_UUID_Info("UUID"));qDebug()<< QString::fromWCharArray(SMBIOS_Get_UUID_Info("SysSerialNumber"));qDebug()<< QString::fromWCharArray(SMBIOS_Get_UUID_Info("BoardSerialNumber"));qDebug()<< QString::fromWCharArray(SMBIOS_Get_UUID_Info("SysSKU"));SMBIOS_Free();
动态链接库 创建 Qt 为例
在 Pro文件中添加如下内容
DEFINES += WINIO_DLL
winapi.h
#ifndef WINAPI_H#define WINAPI_H#include<QObject>#include<windows.h>#include<winioctl.h>// 定义内存映射结构#include"winio_nt.h"#include<QLibrary>#include<QDebug>// 函数定义typedefbool( _stdcall* LP_InitializeWinIo)();typedefbool( _stdcall* LP_ShutdownWinIo)();typedefbool( _stdcall* LP_InstallWinIoDriver)(PSTR pszWinIoDriverPath,bool IsDemandLoaded);typedefbool(_stdcall* LP_RemoveWinIoDriver)();typedefbool( _stdcall* LP_GetPortVal)(WORD wPortAddr, PDWORD pdwPortVal, BYTE bSize);typedefbool( _stdcall* LP_SetPortVal)(WORD wPortAddr, DWORD dwPortVal, BYTE bSize);typedefbool( _stdcall* LP_GetPhysLong)(PBYTE pbPhysAddr, PDWORD pdwPhysVal);typedefbool( _stdcall* LP_SetPhysLong)(PBYTE pbPhysAddr, DWORD dwPhysVal);typedefPBYTE(_stdcall* LP_MapPhysToLin)(tagPhysStruct &PhysStruct);typedefbool(_stdcall* LP_UnmapPhysicalMemory)(tagPhysStruct &PhysStruct);typedefbool(_stdcall* LP_OpenSioDecode)();typedefbool(_stdcall* LP_CloseSioDecode)();classWinAPI :public QObject{ Q_OBJECTpublic:explicitWinAPI(QObject *parent = nullptr); ~ WinAPI() override;// 共有供外接访问的 API 接口public slots:// 1,加载动态链接库boolLoad_WinIO();// 2, 函数调用端口 以BIOS源代码同步// 2.1 读取SuperIO 数据BYTE IoRead8(WORD port); //参数: 端口地址 ,端口值 BYTE 8位WORD IoRead16(WORD port); //参数: 端口地址 ,端口值 WORD 16位DWORD IoRead32(WORD port); //参数: 端口地址 ,端口值 DWORD 32 wei// 2,2 向SuperIO中写入数据boolIoWrite8(WORD port, DWORD val);boolIoWrite16(WORD port, DWORD val);boolIoWrite32(WORD port, DWORD val);// 2.3 读取内存中的数据DWORD GetMemory(PBYTE pbPhysAddr); //获取内存中的值// 2.4 向内存中写入数据boolSetMemory(PBYTE pbPhysAddr, DWORD dwPhysVal); // 向内存中写入数据// 2.5 向内存空间映射地址PBYTE MapMemoryToLin(tagPhysStruct &PhysStruct);boolUnMapMemoryToLin(tagPhysStruct &PhysStruct);// 私有接口private slots:inlinevoidDebug(QString Content){ qDebug()<<Content;emit Send_Log(Content + "\n"); }signals:voidSend_Log(QString log);private:// HMODULE m_hDllFile = nullptr; // 初始化WinIO 句柄// 对函数指针进行重定义。 LP_InitializeWinIo InitializeWinIo = nullptr; LP_ShutdownWinIo ShutdownWinIo = nullptr; LP_GetPhysLong GetPhysLong = nullptr; LP_SetPhysLong SetPhysLong = nullptr; LP_SetPortVal SetPortVal = nullptr; LP_GetPortVal GetPortVal = nullptr; LP_MapPhysToLin MapPhysToLin = nullptr; LP_UnmapPhysicalMemory UnmapPhysicalMemory = nullptr; LP_InstallWinIoDriver InstallWinIoDriver = nullptr; LP_RemoveWinIoDriver RemoveWinIoDriver = nullptr; LP_OpenSioDecode OpenSioDecode = nullptr; LP_CloseSioDecode CloseSioDecode = nullptr;};#endif// WINAPI_H
winapi.cpp
#include"winapi.h"#include<QCoreApplication>#include<QDir>WinAPI::WinAPI(QObject *parent) : QObject{parent}{//1,初始化WinIO Debug("WinAPI 构造初始化加载WinIO");this->Load_WinIO();if(InitializeWinIo()) { Debug("二次初始化成功!"); }}WinAPI::~WinAPI(){ Debug("WinAPI 析构函数");if(ShutdownWinIo != nullptr) // 确保指针已经初始化成功 { RemoveWinIoDriver(); ShutdownWinIo(); }}boolWinAPI::Load_WinIO(){//加载动态链接库,并通过GetProcAddress方法调用取函数空间地址// @ 传入所需的动态链接库// 返回m_hDllFile将作为GetProcAddress去调用WinIO的函数地址使用int Fail = 0;#ifdef Q_OS_WIN64// 64 位 Windows 版本 m_hDllFile = LoadLibrary(L"drv64.dll");#else// 32 位 Windows 版本 m_hDllFile = LoadLibrary(L"32drv.dll");#endifif(m_hDllFile == nullptr) { Debug("1,Load 失败,请检查 WinIO 文件是否与exe same location!!! "); Fail ++; }else {// 对函数指针初始化OEMLib_InitializeWinIo -> InitializeWinIo InitializeWinIo=(LP_InitializeWinIo)::GetProcAddress(m_hDllFile,"InitializeWinIo");if(nullptr == InitializeWinIo) { Debug( "InitializeWinIo 加载函数指针失败"); Fail ++; }//qDebug()<< "后:"<< OEMLib_InitializeWinIo;// 函数指针初始化 OEMLib_ShutdownWinIo -> ShutdownWinIo ShutdownWinIo=(LP_ShutdownWinIo)::GetProcAddress(m_hDllFile,"ShutdownWinIo");if(nullptr == ShutdownWinIo) { Debug("ShutdownWinIo 加载函数指针失败"); Fail ++; } InstallWinIoDriver=(LP_InstallWinIoDriver)::GetProcAddress(m_hDllFile,"InstallWinIoDriver");if(nullptr==InstallWinIoDriver) { qDebug()<< "InstallWinIoDriver 加载函数指针失败"; Fail ++; } RemoveWinIoDriver=(LP_RemoveWinIoDriver)::GetProcAddress(m_hDllFile,"RemoveWinIoDriver");if(nullptr==RemoveWinIoDriver) { qDebug()<< "RemoveWinIoDriver 加载函数指针失败"; Fail ++; }// 函数指针初始化 OEMLib_GetPhysLong -> GetPhysLong GetPhysLong=(LP_GetPhysLong)::GetProcAddress(m_hDllFile,"GetPhysLong");if(nullptr == GetPhysLong) { Debug( "GetPhysLong 加载函数指针失败"); Fail ++; }// 函数 OEMLib_SetPhysLong -> SetPhysLong SetPhysLong = (LP_SetPhysLong)::GetProcAddress(m_hDllFile,"SetPhysLong");if(nullptr == SetPhysLong) { Debug( "SetPhysLong 加载函数指针失败"); Fail ++; } SetPortVal=(LP_SetPortVal)::GetProcAddress(m_hDllFile,"SetPortVal");if(nullptr == SetPortVal) { Debug("SetPortVal 加载函数指针失败"); Fail ++; }// 初始化操作 GetPortVal = (LP_GetPortVal)::GetProcAddress(m_hDllFile, "GetPortVal");if( nullptr== GetPortVal) { Debug("GetPortVal 加载函数指针失败"); Fail ++; } MapPhysToLin=(LP_MapPhysToLin)::GetProcAddress(m_hDllFile,"MapPhysToLin");if(nullptr==MapPhysToLin) { Debug( "MapPhysToLin 加载函数指针失败"); Fail ++; } UnmapPhysicalMemory = (LP_UnmapPhysicalMemory)::GetProcAddress(m_hDllFile,"UnmapPhysicalMemory");if(nullptr == UnmapPhysicalMemory) { Debug( "UnmapPhysicalMemory 加载函数指针失败"); Fail ++; } OpenSioDecode = (LP_OpenSioDecode)::GetProcAddress(m_hDllFile,"OpenSioDecode");if(nullptr == OpenSioDecode) { Debug( "OpenSioDecode 加载函数指针失败"); Fail ++; } CloseSioDecode = (LP_CloseSioDecode)::GetProcAddress(m_hDllFile,"CloseSioDecode");if(nullptr == CloseSioDecode) { Debug( "CloseSioDecode 加载函数指针失败"); Fail ++; }// 对WinIO进行初始化bool status = InitializeWinIo();if(status) { Debug("1,WinIO初始化成功"); }else { Debug("WinIO初始化失败,失败原因请检查如下:"); Fail ++; }; }if(Fail == 0) {returntrue; }else {returnfalse; }}BYTE WinAPI::IoRead8(WORD Port){ DWORD val = 0x0;if(GetPortVal(Port,&val,1)) // Can be 1 (BYTE), 2 (WORD) or 4 (DWORD). { Debug("十六进制:" + QString::number(val,16) + " 二进制:" + QString::number(val,2) ); }else { Debug("读取 0x " + QString::number(Port,16)+ "数据失败"); };return val;}WORD WinAPI::IoRead16(WORD Port){ DWORD val = 0x0;if(GetPortVal(Port,&val,2)) // Can be 1 (BYTE), 2 (WORD) or 4 (DWORD). { Debug("十六进制:" + QString::number(val,16) + " 二进制:" + QString::number(val,2)); }else { Debug("读取 0x" + QString::number(Port,16) + "数据失败"); };return val;}DWORD WinAPI::IoRead32(WORD Port){ DWORD val = 0x0;if(GetPortVal(Port,&val,4)) // Can be 1 (BYTE), 2 (WORD) or 4 (DWORD). { Debug("十六进制:" + QString::number(val,16) + " 二进制:" + QString::number(val,2) ) ; }else { Debug( "读取 0x" + QString::number(Port,16) + "数据失败" ); };return val;}boolWinAPI::IoWrite8(WORD port, DWORD val){if(SetPortVal(port,val,1)) { Debug( "向:" + QString::number(port,16) + "中写入:" + QString::number(val,16) + "成功");returntrue; }else { Debug( "向:" + QString::number(port,16) + "中写入:" + QString::number(val,16) + "失败");returnfalse; }}boolWinAPI::IoWrite16(WORD port, DWORD val){if(SetPortVal(port,val,2)) { Debug( "向:" + QString::number(port,16) + "中写入:" + QString::number(val,16) + "成功");returntrue; }else { Debug( "向:" + QString::number(port,16) + "中写入:" + QString::number(val,16) + "失败");returnfalse; }}boolWinAPI::IoWrite32(WORD port, DWORD val){if(SetPortVal(port,val,4)) { Debug( "向:" + QString::number(port,16) + "中写入:" + QString::number(val,16) + "成功");returntrue; }else { Debug( "向:" + QString::number(port,16) + "中写入:" + QString::number(val,16) + "失败");returnfalse; }}DWORD WinAPI::GetMemory(PBYTE pbPhysAddr){ DWORD val = 0x0;if(GetPhysLong) {if(GetPhysLong(pbPhysAddr,&val)) { Debug( QString("GetMemory 数据读取 Successful " )); }else { Debug( QString( "GetMemory 数据读取 Failed ") ); }; }return val;}boolWinAPI::SetMemory(PBYTE pbPhysAddr, DWORD dwPhysVal){if(SetPhysLong(pbPhysAddr,dwPhysVal)) { Debug("SetMemory 写入 成功 Successful");returntrue; }else { Debug("SetMemory 写入 失败 Failed");returnfalse; }}PBYTE WinAPI::MapMemoryToLin(tagPhysStruct &PhysStruct){return MapPhysToLin(PhysStruct);}boolWinAPI::UnMapMemoryToLin(tagPhysStruct &PhysStruct){return UnmapPhysicalMemory(PhysStruct);}
代码结构
头文件 中定义 引用所需要的头文件 供动态和静态调用时使用 Source 文件中定义头文件实现函数

头文件

基础函数
相关细节,可以查看本文件夹的WINIO源码
CreateFile
此函数可以创建新文件或打开现有文件,必须指定文件名,创建文件说明和其他属性。
创建或打开文件或 I/O 设备。常用的 I/O 设备有:文件,文件流,目录,物理磁盘,卷,控制台缓冲区,磁带驱动器,通信资源,邮筒和管道。该函数返回一个句柄,该句柄可用于根据文件或设备以及指定的标志和属性访问文件或设备以获取各种类型的 I/O.
HANDLE WINAPI CreateFile( _In_ LPCTSTR lpFileName,//要创建或打开的文件或设备的名称 _In_ DWORD dwDesiredAccess,// 所请求的文件或设备访问权限,可以被概括为读,写,两者或非 _In_ DWORD dwShareMode,// 文件或设备的请求共享模式,可以读取,写入,删除,全部或全部删除 _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,//指向SECURITY_ATTRIBUTES结构的指针,该结构包含两个独立相关的数据成员,一个可选的安全描述符以及一个布尔值,该值确定返回的句柄是否可以被子进程继承,若为NULL则由CreateFile返回的句柄不能由应用程序可能创建的任何子进程继承,并且与返回句柄关联的文件或设备将获得默认安全描述符 _In_ DWORD dwCreationDisposition,//采用存在或不存在的文件或设备的操作 _In_ DWORD dwFlagsAndAttributes,// 文件或设备属性和标志 _In_opt_ HANDLE hTemplateFile// 具有GENERIC_READ访问权限的模版文件的有效句柄);
DeviceIoControl
DeviceIoControl是一个Windows API函数,用于与设备驱动程序进行通信。这个函数可以用来发送控制代码和接收响应
BOOL DeviceIoControl( HANDLE hDevice, // 设备的句柄 DWORD dwIoControlCode, // 控制代码 LPVOID lpInBuffer, // 输入缓冲区 DWORD nInBufferSize, // 输入缓冲区大小 LPVOID lpOutBuffer, // 输出缓冲区 DWORD nOutBufferSize, // 输出缓冲区大小 LPDWORD lpBytesReturned, // 返回的字节数 LPOVERLAPPED lpOverlapped // 重叠结构);
实现函数
实现对 指定内存的访问接口。实现对某个 SuperIO的某个bit位的控制。实现对 SumBus的访问
在
winIo的头文件中加入如下函数,并在相关的cpp函数中进行实现。
// 添加自定义函数 IT8786 系列的WINIO_API bool _stdcall IoWrite8(UINT8 Register, UINT8 Value);WINIO_API UINT8 _stdcall IoRead8(UINT8 Register);// 使 ITE 使能 superIo访问 接口已验证 可行WINIO_API bool _stdcall OpenSioITEDecode();WINIO_API bool _stdcall CloseSioITEDecode();// 操作SUPERUI 单个寄存器的 单个Bit值// 参数:读取Port 读取的 bit位// 返回值:返回bit位是 0还是 1WINIO_API int _stdcall IoRead8_Single_Bit(WORD port, int bit);// 参数 Port 写入Bit 写入值0/1WINIO_API bool _stdcall IoWrite8_Single_Bit(WORD addr, int bit, int bit_value);// 以机型// 控制某个GPIO 的函数 传入参数 GPIO pin编号 ,高1 或者 低0// SMBIOS访问 控制// 访问SumBus函数封装于此WINIO_API bool _stdcall SmbusWriteByte(WORD slav_address, WORD offset_address, WORD write_data);WINIO_API bool _stdcall SmbusReadByte(WORD slav_address, WORD offset_address, WORD* read_data);//给接口访问内存 l来进行控制 可行 已验证WINIO_API bool _stdcall MemoryTest();WINIO_API DWORD _stdcall GetMemoryTest();
自定义步骤
首先在 winio.h中定义头文件然后在具体的函数中,可自己 定义应用头文件,并对头文件进行实现在进行自定义函数封装时,尽量调用不要过于封装,避免出现访问失败问题。例如在访问 SMBus时就调用了SuperIO的封装,SuperIO有调用 API 接口,API 调用系统接口,一个函数调用太多,容易出问题。

SuperIO自定义函数
// ---------------------------------------------------- //// 对SuperIO相关的定制化函数 //// ---------------------------------------------------- //#include<windows.h>#include"winio.h"UINT8 IT8728F_CONFIG_INDEX = 0x2e;UINT8 IT8728F_CONFIG_DATA = 0x2f;bool _stdcall IoWrite8(UINT8 Register, UINT8 Value){if (SetPortVal((WORD)Register, (WORD)Value, 1)) {returntrue; }else {returnfalse; }}UINT8 _stdcall IoRead8(UINT8 Register){ DWORD portValue = 0x0; // 用于存储读取的值 GetPortVal(Register, &portValue, 1);return portValue; // 只返回低8位}bool _stdcall OpenSioITEDecode(){ IoWrite8(IT8728F_CONFIG_INDEX, 0x87); IoWrite8(IT8728F_CONFIG_INDEX, 0x01); IoWrite8(IT8728F_CONFIG_INDEX, 0x55); IoWrite8(IT8728F_CONFIG_INDEX, 0x55);returntrue;}bool _stdcall CloseSioITEDecode(){ IoWrite8(IT8728F_CONFIG_INDEX, 0x02); IoWrite8(IT8728F_CONFIG_DATA, 0x02);returntrue;}int _stdcall IoRead8_Single_Bit(WORD port, int bit){ BYTE Value = 0x0; DWORD val = 0x0;if (GetPortVal(port, &val, 1)) // Can be 1 (BYTE), 2 (WORD) or 4 (DWORD). {// 获取 val 的第 bit 位的值if (bit < 0 || bit >= 32) // 确保 bit 在合法范围内 {return-1; // 返回错误代码 }// 使用位操作获取对应的 bit 位int bitValue = (val >> bit) & 1; // 右移 bit 位并与 1 进行与操作return bitValue; // 返回对应 bit 位的值(0或1) }else {return-1; }}bool _stdcall IoWrite8_Single_Bit(WORD addr, int bit, int bit_value){ BYTE Curr_Value = IoRead8(addr);// 根据条件与或操作来更新对应的值if (bit_value == 1) {// 将第bit位设置为1 Curr_Value |= (1 << bit); }else {// 将第bit位设置为0 Curr_Value &= ~(1 << bit); }// 将更新后的值 写入if (SetPortVal(addr, Curr_Value, 1)) {returntrue; }else {returnfalse; }returntrue;}
内存测试函数
bool _stdcall MemoryTest(){if (SetPhysLong(0x00000000, 0x44000200)) {returntrue; }else {returnfalse; }}DWORD _stdcall GetMemoryTest(){ DWORD val = 0x0; GetPhysLong(0x00000000, &val);return val;}
SMBus自定义函数
// ---------------------------------------------------- //// 对SMBus进行访问的相关函数 //// ---------------------------------------------------- //#include<windows.h>#include"winio.h"WORD Sumbus_base = 0xf000;// 使用动态链接库 来进行 IO Space的函数 请直接使用原生的函数,不要嵌套那么多层,否则会出现访问失效的问题bool _stdcall SmbusWriteByte(WORD slav_address, WORD offset_address, WORD write_data){int Timer_Num = 0; DWORD val = 0x0; // 存储读到的值// 初始化超时时间位 0unsignedlong timeout = 0;// 使用outb方法将write_date的值写入到I/O端口地址0xf005/* 端口IO * port I/O 地址,此地址位虚拟地址 * data 位写入数据 */ SetPortVal(Sumbus_base + 0x05,write_data,1);// 将0x1f的值写入指定的I/O端口地址0xf000 SetPortVal(Sumbus_base, 0x1f, 1);while (1) {// inb读取指定寄存器的值 0xf000 DWORD val = 0x0; GetPortVal(Sumbus_base, &val, 1); // Can be 1 (BYTE), 2 (WORD) or 4 (DWORD).// 暂停10微妙//usleep(10); Sleep(10);// 检查对应的值最低为是否为 0if ((val & 0x1) == 0) {break; }// 超市时间 timeout++;// 10微妙一次,相当于超过一秒,访问超时if (timeout > 100) { Timer_Num++;break; } }// 将slav_address基地址写到0xf004 SetPortVal(Sumbus_base + 0x04, slav_address, 1); SetPortVal(Sumbus_base + 0x03, offset_address, 1);// 将0x48 写入到 0xf002 SetPortVal(Sumbus_base + 0x02, 0x48, 1);// 将超时进行初始化 timeout = 0;while (1) {// 读取0xf000的数据//tempvalue= io->inb(Sumbus_base); GetPortVal(Sumbus_base, &val, 1); // Can be 1 (BYTE), 2 (WORD) or 4 (DWORD).//usleep(10); Sleep(10);// 判断最低为是否为0if ((val & 0x1) == 0) {break; } timeout++;// 超时算上10微妙就为1秒if (timeout > 100) { Timer_Num++;break; } }// 如果tempvalue为零 则写入成功if ((val & 0x1C) != 0) { Timer_Num++; }if (Timer_Num != 0) {returnfalse;// }else {returntrue; }returntrue;}bool _stdcall SmbusReadByte(WORD slav_address, WORD offset_address, WORD* read_data){int Timer_Num = 0;// 记录读取的值 DWORD val = 0x0;// 设置超时时间unsignedlong timeout = 0;// 将0x1f写入0xf000 SetPortVal(Sumbus_base, 0x1f, 1);while (1) {// 判断是否写入 GetPortVal(Sumbus_base, &val, 1); Sleep(10);// 判断最低为是否为0if ((val & 0x1) == 0) {break; } timeout++;//if (timeout > 100) { Timer_Num++;// perror("Smbus timeout!");break; } }// 将基地址+1存入寄存器IO 0xf004// +1 为读. SetPortVal(Sumbus_base + 0x04, (slav_address + 1), 1);// 将偏移量 写入 0xf003 SetPortVal(Sumbus_base + 0x03, offset_address, 1);// 将0x48 写入 0x002 SetPortVal(Sumbus_base + 0x02, 0x48, 1); timeout = 0;while (1) { GetPortVal(Sumbus_base, &val, 1); Sleep(10);if ((val & 0x1) == 0) {break; } timeout++;if (timeout > 100) { Timer_Num++;// perror("Smbus timeout!");break; } }if ((val & 0x1C) != 0) { Timer_Num++; }else { GetPortVal(Sumbus_base, &val, 1); *read_data = val; }if (Timer_Num != 0) {returnfalse; }}
签名问题
通过第三方机构对驱动SYS文件签名,记得修改对应的驱动服务文件,详见提交:
https://github.com/foryouos/WinIoNote/commit/935889f81d4c14884c5a269337ba68e73ad0b361
开源地址
DumpSMBIOS 开源项目[3] WinIoNote[4]
参考资料
-
CreateFile 函数详细解析[5] -
[下载 Windows 驱动程序工具包 (WDK) – Windows drivers | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows-hardware/drivers/download-the-wdk “下载 Windows 驱动程序工具包 (WDK “下载 Windows 驱动程序工具包 (WDK) – Windows drivers | Microsoft Learn”) – Windows drivers | Microsoft Learn”) -
[编写 Hello World Windows 驱动程序 (KMDF) – Windows drivers | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows-hardware/drivers/gettingstarted/writing-a-very-small-kmdf–driver “编写 Hello World Windows 驱动程序 (KMDF “编写 Hello World Windows 驱动程序 (KMDF) – Windows drivers | Microsoft Learn”) – Windows drivers | Microsoft Learn”)
以前的 WDK 版本和其他下载 – Windows drivers | Microsoft Learn: https://learn.microsoft.com/zh-cn/windows-hardware/drivers/other-wdk-downloads
[2]节对象的: https://learn.microsoft.com/zh-cn/windows-hardware/drivers/
[3]DumpSMBIOS开源项目: https://github.com/KunYi/DumpSMBIOS
[4]WinIoNote: https://github.com/foryouos/WinIoNote 查看原文
[5]CreateFile 函数详细解析: https://blog.csdn.net/li_wen01/article/details/80142931
夜雨聆风
