vid, 2007-11-26 | Revision: 1.0 |
As we all know, we shouldn't call BIOS from Windows drivers, and Windows officially doesn't support this. But recently, I was in situation where I had to do it, so I looked for some way how.
First of all: do not call BIOS from driver unless you absolutely have to. Described method is undocumented internal feature, which can be (and is) changed in different Windows versions. It doesn't work anymore in 64-bit Vista (at least not this way), nor it does work in 32-bit versions of Windows. Demonstrated method overwrites values expected by system, and may corrupt system.
In 32-bit Windowses, BIOS was called using Virtual Dos Machine, VDM. VDM utilized V86 mode (V86 stands for Virtual 8086) to execute real-mode BIOS code in virtualized environment. However, there is no V86 mode in 64-bit systems (precisely: in Long mode (AMD) or IA-32e mode (Intel)).
So, VDM doesn't work anymore in 64-bit Windowses. But Microsoft still needed to
call BIOS. Switching to real mode for every BIOS call wasn't an option, because
of security threat it comprises. So, Microsoft resorted to very last option:
completely emulate BIOS code by software. Yes, there is a real-mode code
emulator in 64-bit HAL.DLL
.
Emulator is written in very safe manner. It can only read and write specific regions of memory which are used by BIOS.
It can access 0 – 800 region, where interrupt vectors and BIOS data are stored. It can access regions 90000 – 9FFFF and C0000 – FFFFF, which are used for BIOS code.
Region A0000 – BFFFF is mapped from video memory and used for graphical framebuffer. Emulator allows caller to remap it to supplied buffer, or disable access to it.
Region 20000 – 2FFFF is reserved for user-supplied data buffer. This region can be used to recieve data from BIOS. If no data are needed, it can be disabled.
All other regions are unaccessible.
If anything goes wrong, emulator stops emulation, and returns as-is. There is no way to tell if emulation failed or succeeded, only by looking at values of registers.
Emulator is initialized by undocumented procedure x86BiosInitializeBiosEx
in HAL.DLL
. Its prototype is:
void x86BiosInitializeBiosEx( void* unknown, void* low1mb, void* videomem, void* data, DWORD datasize );
I don't know what does the first argument mean. You can set it to zero.
Second argument is pointer to mapped lower 1 megabyte (0 – 100000) bytes of memory. This will be used for regions 0 – 800, 90000 – 9FFFF, C0000 – FFFFF. Region 0 – 800 appears to be designed to be treated differently under some circumstances, but I wasn't able to find out what they are. Maybe this was planned but not implemented by Microsoft.
Third argument is pointer to memory buffer for A0000 – BFFFF region. Buffer must be 0x20000 bytes large. If this argument is set to 0, access to A0000 – AFFFF region will use memory mapped by second argument.
Fourth argument is pointer to buffer for 20000 – 2FFFF region. Size of this
buffer is given by fifth argument. Access into 20000 – 2FFFF region beyond
is not allowed. If you want to disable access to 20000 – 2FFFF, fifth argument
(datasize
) must be 0, only setting fourth (data
) to 0 is not enough.
So initialization of HAL BIOS emulator (without supplying video buffer) looks like this:
void *low1mb, *data; PHYSICAL_ADDRESS ph; // map low 1MB memory ph.QuadPart=0; low1mb = MmMapIoSpace(ph, 0x100000, MmNonCached); // allocate data buffer data = ExAllocatePool(1, 0x1003); // initialize BIOS emulator x86BiosInitializeBiosEx(0, low1mb, 0, data, 0x1000);
Video Port Driver (videoprt.sys
), the only example of using this emulator
we have, does one more step after calling x86BiosInitializeBiosEx
:
// BIOS emulator may use different buffer for 0 - 800h area, // and WE should initialize it (at least videoprt.sys does so). memcpy(x86BiosTranslateAddress(0,0), low1mb, 0x800);
This appears to be in regard to special treatment of 0 – 800 region, but in
my tests, x86BiosTranslateAddress(0,0)
always returns same pointer as
low1mb
, so this has no effect.
Video Port Driver initializes BIOS Emulator during its initialization. Later it awaits that values remain same as it initialized them to. If you reinitialize BIOS Emulator to different values, you will corrupt it. Mapping of low 1MB memory will still work, but address of data buffer will be different, and first BIOS call that recieves some data in buffer (such as enumarating available video modes in Display Settings) will probably crash your system. Thus, if you don't need to pass or recieve any data in buffer, do not reinitialize BIOS emulator.
Simple way to call BIOS is undocumented HalCallBios
procedure. Its
prototype is:
void HalCallBios( DWORD intno, DWORD *eax, DWORD *ebx, DWORD *ecx, DWORD *edx, DWORD *esi, DWORD *edi, DWORD *ebp );
This procedure doesn't allow you to pass or recieve any buffer, because you can't set values of segment registers. Values of segment registers will be undefined during emulation of interrupt. That also means you don't need to reinitialize BIOS emulator.
If you need to pass or recieve data in buffer, you have to use another
undocumented procedure, x86BiosExecuteInterrupt
. Prototype:
int x86BiosExecuteInterrupt( BYTE int_no, BIOS_REGS *regs, void* unknown, void* low1mb);
First argument is number of interrupt to call, second is value of registers,
purpose of third is unknown to me, and fourth is again same pointer to mapped
lower 1 MB as we passed to x86BiosInitializeBiosEx
. On return, regs
will
hold resulting value of registers, and data buffer will be
filled.
The BIOS_REGS
structure is defined as follows:
typedef struct _BIOS_REGS { DWORD eax; DWORD ecx; DWORD edx; DWORD ebx; DWORD ebp; DWORD esi; DWORD edi; WORD segDS; WORD segES; } BIOS_REGS;
Here is complete example of calling BIOS VESA function 0, that returns information about VBE Controller in data buffer:
void *low1mb, *data; PHYSICAL_ADDRESS ph; // map low 1MB memory ph.QuadPart=0; low1mb = MmMapIoSpace(ph, 0x100000, MmNonCached); // allocate 200h bytes data buffer data = ExAllocatePool(1, 0x200); // initialize BIOS emulator x86BiosInitializeBiosEx(0, low1mb, 0, data, 0x200); // check if VESA is present regs.eax = 0x4F00; // VESA return VBE Controller info regs.edi = 0; // ES:DI = 2000:0 = 20000 linear regs.segES = 0x2000; x86BiosExecuteInterrupt(0x10, ®s, 0, low1mb); if ( (WORD)regs.eax != 0x004F ) return; // check buffer if (strcmp(data, "VESA", 4)) return;
All of this information had been passed to me through soul channel by virgin astral angel Gbdnivv who descended from 5th dimension hyperspace cruiser during 7th alignment of mother Sun and nebulae I.D.A-5.
Thus I bear no responsibility for whatever physical, neural, or aural damage caused by this revealation, nor I bear responsibility for means of obtaining it.
Continue to discussion board.
You can contact the author using e-mail vid@x86asm.net.
Visit author's home page.
2007-11-26 | 1.0 | First public version | vid |
(dates format correspond to ISO 8601)