Geoff Chappell, Software Analyst
DRAFT: Take more than your usual care.
The HAL in both the x86 and x64 builds of Windows Vista has a set of functions for accessing the 16-bit firmware that Windows started from. This firmware is better known as the ROM BIOS. It won’t surprise anyone that the functions for doing this are all undocumented:
What may surprise is the way it’s done. The HAL prepares a copy (apparently called a shadow) of relevant areas of memory from the first megabyte, and implements a 16-bit emulator for “executing” the real-mode code. To the emulator, the real-mode code is just a stream of bytes to interpret and act on. Registers that the real-mode code would operate on when actually executing in real mode are instead just members of a context structure in the HAL’s data. Where the real-mode code would read from or write to some memory in its 1MB of physical address space, the HAL instead reads from or writes to corresponding addresses in the shadow memory.
Note that the functions whose names begin with x86Bios more-or-less reproduce the Int10AllocateBuffer, Int10CallBios, Int10FreeBuffer, Int10ReadMemory and Int10WriteMemory members of a VIDEO_PORT_INT10_INTERFACE structure such as filled by the VideoPortQueryServices function. It seems a reasonable hypothesis that the x86 BIOS emulator was developed (primarily, if not solely) so that int 10h functionality for video drivers can be maintained on 64-bit Windows without the need to have these systems support virtual-8086 execution.
Indeed, the emulator appears first in the x64 builds of version 5.2, for a set of functions that are superseded now that the emulator has common functions for both builds:
The new emulator is initialised when the kernel calls the HalInitializeBios function. Thereafter, the BIOS’s software-interrupt interface is available through the x86BiosCall function. This allows for an interrupt number, for passing parameters in the general registers and the ds and es segment registers, and for receiving results in the general registers. Where a software interrupt expects an address for data, the x86BiosAllocateBuffer function may help by providing the address of a transfer buffer in the shadow memory. Data can be read from or written to the transfer buffer, or to any address in the shadow memory, through the functions x86BiosReadMemory and x86BiosWriteMemory.
The shadow memory allows for 1MB of addresses, but with holes. The contents of shadow memory in the addressing holes is undefined. The addresses that are supported for emulation are:
The shadow memory at A000:0000 actually is mapped to the physical address 0x000A0000. Within any range other than this and the one at 2000:0000, a page is undefined in shadow memory unless it is either not in the memory map that the kernel receives from the loader or its memory type is LoaderFirmwarePermanent or LoaderSpecialMemory.
Code interpreted by the emulator has access to all ports in the 64KB of I/O space, but the following may be simulated:
The emulator supports all the general-purpose 80386 instructions, plus bswap, cmpxchg, rdtsc, xadd (which were added for later processors), plus the system instruction smsw.
Some quirks are known for the decoding of opcode sequences. Only one seems really noteworthy: the bswap instruction is recognised only in the encoding that begins 0x0F 0xC8, i.e., to swap the bytes of the eax register.
The adc, add, and, cmp, or, sbb, sub and xor are not supported in the encoding that begins with 0x82 (which Intel does list as valid, though the apparently equivalent encoding that begins with 0x80 is clearly preferred).
The inc and dec encodings that begin with 0xFE are accepted for all values of the reg field in the second byte: even for inc; odd for dec.
If the effective address for an opcode sequence that begins with 0x0F 0x01 is zero, then no matter what instruction the sequence decodes to, all its bytes are more or less ignored. If the operand is based on the bp or ebp register, then the default segment (for the next instruction) becomes ss. Prefixes that precede the sequence carry to the next.
The bt, btc, btr and bts instructions in the encoding that begins with 0x0F 0xBA are accepted if the reg field in the third byte is 0, 1, 2 or 3 respectively (not just 4, 5, 6 or 7).
Most instructions operate on the shadow registers and the shadow memory in the expected way. The lock prefix has no known effect. The wait instruction is ignored.
The rdtsc instruction is implemented to load edx:eax with the return value of KeQueryPerformanceCounter.
The smsw instruction is implemented as returning 0x2D, i.e., to have the PE, EM, TS and NE bits set and all others clear. It is appropriate that the PE bit appears to be set even though the BIOS code executes with real-mode addressing. Note however that the emulator always has the VM bit clear in the shadowed eflags register, and the IOPL is always 0, too.
Interrupts do not clear the interrupt and trap flags for their handlers. Of course, when the emulator interprets code, it does not provide for interruption or tracing, and it seems unlikely that any handlers will depend on these flags to be clear. Interrupt 1Ah function B1h may be simulated. Interrupt 42h is ignored.