Introduction
On Windows Vista and Windows 7, display driver can adhere to either XPDM or WDDM.
The previous article covered XPDM on Windows 7 case. On Windows 8+, the only possible choice for display driver is to adhere to WDDM. In WDDM, there are no longer separate drivers for video and display, we have only display port and display miniport.
According to this MSDN article, there are three types of display miniport drivers introduced:
- Full Graphics Driver
- Display Only Driver
- Render Only Driver
On Windows 8 (did not check on Windows 10), we have default BasicDisplay.sys and BasicRender.sys drivers. VirtualBox supplies full graphics driver VBoxVideoW8.sys that can replace default pair.
All this means that we can no longer call video driver because there is no EngDeviceIoControl
involved. So we have to get video mode and set it manually. To figure out how to do this, I stepped into IOCTL_VIDEO_SET_CURRENT_MODE
call:
VgaDeviceIoControl(pDeviceObject, IOCTL_VIDEO_SET_CURRENT_MODE,
&VideoMode, sizeof(VideoMode), NULL, 0, &BytesReturned, FALSE);
and found out that video driver uses x86BiosCall
function to set video mode. With this in mind, I went to Windows 8 and set breakpoint on x86BiosCall
. I found two x86BiosCall
calls inside BasicDisplay!BiosSetDisplayMode
function. The first call sets specified video mode and the second call checks current video mode (whether mode was successfully set).
Using the Code
Let's see the code. User mode part remains the same, so I won't duplicate it here. We need to disable gpus, run driver, refresh desktop, enable gpus. With this in mind, let's see the driver itself.
#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);
NTKERNELAPI VOID VidResetDisplay(BOOLEAN HalReset);
NTKERNELAPI VOID VidDisplayString(PUCHAR String); }
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;
}
You can see this link for VESA BIOS Extensions
commands.
See ReactOS
source repository for boot video functions prototypes.
If we will try to compile our driver, we will get the following linker errors:
x86 build:
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 build:
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
To import from BOOTVID.DLL, we need to create import library and add it to the linker input for our driver project. To do this, we need dumpbin
and lib
tools. On my system, they are located in:
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
Copy BOOTVID.DLL from C:\Windows\System32 to appropriate directory (depending on whether your Windows is 64 bit or not) and run the following cmd
command:
dumpbin /exports BOOTVID.DLL
It will give you exported function names and their ordinals:
Create module definition file and copy it to the appropriate directory (depending on whether you compile driver for x86 or x64). Module definition file BOOTVID.def content:
x86:
EXPORTS
VidDisplayString@4 @5
VidResetDisplay@4 @8
x64:
EXPORTS
VidDisplayString
VidResetDisplay
Note that for x86 build, we need to suffix function name and specify ordinal, whereas for x64 build, we can go with pure function name.
Run the following cmd
command:
lib /def:BOOTVID.def /out:BOOTVID.lib
Import library will be generated:
Now copy import library to your project directory and add it to linker input (Project Properties -> Linker -> Input -> Additional Dependencies).
The reason to directly import from BOOTVID.DLL: Inbv* functions do not work the same way as they work on Windows 7.
After this, the driver will compile fine.