VGA文本模式:地狱之旅与归来 (第二部分)





5.00/5 (4投票s)
本文展示了如何在Windows上进入VGA文本模式并从中返回。
引言
在 Windows Vista 和 Windows 7 上,显示驱动程序可以遵循 XPDM 或 WDDM。
之前的文章涵盖了 Windows 7 上的 XPDM 情况。 在 Windows 8 及更高版本上,显示驱动程序的唯一选择是遵循 WDDM。 在 WDDM 中,不再有单独的视频和显示驱动程序,我们只有显示端口和显示迷你端口。
根据这篇文章,引入了三种类型的显示迷你端口驱动程序
- 完整图形驱动程序
- 仅显示驱动程序
- 仅渲染驱动程序
在 Windows 8 上(未在 Windows 10 上检查),我们有默认的BasicDisplay.sys 和 BasicRender.sys 驱动程序。 VirtualBox 提供了完整的图形驱动程序VBoxVideoW8.sys,可以替换默认配对。
这意味着我们不再可以调用视频驱动程序,因为没有涉及 EngDeviceIoControl
。 因此,我们必须手动获取视频模式并设置它。 为了弄清楚如何做到这一点,我进入了 IOCTL_VIDEO_SET_CURRENT_MODE
调用
VgaDeviceIoControl(pDeviceObject, IOCTL_VIDEO_SET_CURRENT_MODE,
&VideoMode, sizeof(VideoMode), NULL, 0, &BytesReturned, FALSE);
并发现视频驱动程序使用 x86BiosCall
函数来设置视频模式。 牢记这一点,我进入 Windows 8 并设置了 x86BiosCall
上的断点。 我在 BasicDisplay!BiosSetDisplayMode
函数中找到了两个 x86BiosCall
调用。 第一个调用设置指定的视频模式,第二个调用检查当前的视频模式(模式是否设置成功)。
Using the Code
让我们看看代码。 用户模式部分保持不变,所以我不会在这里重复它。 我们需要禁用 GPU,运行驱动程序,刷新桌面,启用 GPU。 牢记这一点,让我们看看驱动程序本身。
#include <wdm.h>
#define DELAY_SECOND -10000000
struct X86_BIOS_REGISTERS
{
ULONG Eax;
ULONG Ecx;
ULONG Edx;
ULONG Ebx;
ULONG Ebp;
ULONG Esi;
ULONG Edi;
USHORT SegDs;
USHORT SegEs;
};
extern "C"
{
NTKERNELAPI BOOLEAN x86BiosCall
(ULONG InterruptNumber, X86_BIOS_REGISTERS *pRegisters); // import from HAL.DLL
NTKERNELAPI VOID VidResetDisplay(BOOLEAN HalReset); // import from BOOTVID.DLL
NTKERNELAPI VOID VidDisplayString(PUCHAR String); // import from BOOTVID.DLL
}
NTSTATUS VgaGetMode(ULONG *pMode)
{
BOOLEAN b;
NTSTATUS Status;
X86_BIOS_REGISTERS Registers;
memset(&Registers, 0, sizeof(Registers));
Registers.Eax = 0x4F03;
b = x86BiosCall(0x10, &Registers);
if ((b) && (Registers.Eax == 0x4F))
{
*pMode = Registers.Ebx;
Status = STATUS_SUCCESS;
}
else Status = STATUS_UNSUCCESSFUL;
return Status;
}
NTSTATUS VgaSetMode(ULONG Mode)
{
BOOLEAN b;
NTSTATUS Status;
X86_BIOS_REGISTERS Registers;
memset(&Registers, 0, sizeof(Registers));
Registers.Eax = 0x4F02;
Registers.Ebx = 0x4000 | Mode;
b = x86BiosCall(0x10, &Registers);
if ((b) && (Registers.Eax == 0x4F))
{
Status = STATUS_SUCCESS;
}
else Status = STATUS_UNSUCCESSFUL;
return Status;
}
void DriverUnload(PDRIVER_OBJECT pDriverObject)
{
}
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
ULONG Mode;
NTSTATUS Status;
LARGE_INTEGER Interval;
Status = VgaGetMode(&Mode);
if (NT_SUCCESS(Status))
{
VidResetDisplay(TRUE);
VidDisplayString((PUCHAR)"HELLO\n");
Interval.QuadPart = DELAY_SECOND * 10;
KeDelayExecutionThread(KernelMode, FALSE, &Interval);
VgaSetMode(Mode);
}
pDriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
有关 VESA BIOS Extensions
命令,请参阅此链接。
有关启动视频函数原型,请参阅 ReactOS
源代码仓库。
如果我们尝试编译我们的驱动程序,我们将收到以下链接器错误
x86 构建
1>Source.obj : error LNK2019:
unresolved external symbol __imp__VidResetDisplay@4 referenced in function _DriverEntry@8
1>Source.obj : error LNK2019:
unresolved external symbol __imp__VidDisplayString@4 referenced in function _DriverEntry@8
x64 构建
1>Source.obj : error LNK2019:
unresolved external symbol __imp_VidResetDisplay referenced in function DriverEntry
1>Source.obj : error LNK2019:
unresolved external symbol __imp_VidDisplayString referenced in function DriverEntry
要从 BOOTVID.DLL 导入,我们需要创建导入库并将其添加到驱动程序项目的链接器输入中。 为此,我们需要 dumpbin
和 lib
工具。 在我的系统上,它们位于
x86
D:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin
x64
D:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\amd64
将 BOOTVID.DLL 从 C:\Windows\System32 复制到适当的目录(取决于您的 Windows 是 64 位还是 32 位),然后运行以下 cmd
命令
dumpbin /exports BOOTVID.DLL
它将为您提供导出的函数名称及其序号
创建模块定义文件并将其复制到适当的目录(取决于您是为 x86 还是 x64 编译驱动程序)。 模块定义文件 BOOTVID.def 内容
x86
EXPORTS
VidDisplayString@4 @5
VidResetDisplay@4 @8
x64
EXPORTS
VidDisplayString
VidResetDisplay
请注意,对于 x86 构建,我们需要在函数名称后加上后缀并指定序号,而对于 x64 构建,我们可以使用纯函数名称。
运行以下 cmd
命令
lib /def:BOOTVID.def /out:BOOTVID.lib
将生成导入库
现在将导入库复制到您的项目目录,并将其添加到链接器输入(项目属性 -> 链接器 -> 输入 -> 附加依赖项)。
直接从 BOOTVID.DLL: Inbv* 函数导入的原因在于,它们在 Windows 7 上的工作方式不同。
此后,驱动程序将可以正常编译。