The Virtual Monitor is a piece of code you need to set up and control tasks in Virtual 8086 Mode.
- Protected mode must be enabled
- Interrupt Service Routines must be supported, and more specifically a working exception handler for GPF.
Well, you mainly have two flavors: either you can create a standalone task that will run as any other task in your OS but usually operates in V86 mode, or you can temporarily switch to V86 mode from the current task, do a simple command and come back.
What you have to know is that some operations are prohibited in V86 mode, but that legacy code like BIOS or DOS programs will issue them nonetheless. So you'll need to hook the GPF handler so that it detects the faulty operation comes from a virtual mode task and defers execution to the monitor. How do i set up a virtual task ?
There are a few things that distinguish the set up of a VM task from the setup of any "ordinary" process in your OS:
- If you are using paging, you need to make sure that the BIOS, the real-mode IVT and any BIOS-related data (e.g. video ram at 0xb8000) is mapped at expected virtual address. This can be done simply by Identity Paging the first megabyte of this process' address space or you may wish to give private copies of those locations to different VM tasks.
- In your Task Control Block, when you set up the registers on the stack image for the initial run, you have to initialize some additional flags (check out Tim Robinson's tutorial, really). Make sure you at least have EFLAGS=VM|IF, that is, 0x20202. The ip/cs value has to be located inside an area which can be accessed by real mode and you should better have the stack aligned to some 0xffff address.
- You will also need to set up some additional fields: the segment registers, which the processor pops off the stack upon return to a vm86 process Best you make a stack image structure for vm86 tasks too: say vm86_context_t.
- If you want to allow V86 code to access ports without a GPF, you will need to extend the TSS by 8192 bytes (enough for 65,536 ports with one bit per port), point the I/O map field to the start of the bitmap, and set all the bits to zeroes.
Mark II: for the region from 0x100000 to 0x1fffef (iirc) you can map any pages you want. Only the first mb needs to be identity mapped.
- Task Control Block
- the structure your kernel uses for Task bookkeeping: the processor registers dump, the page directory, time to run, priority etc.
Role of General Protection Faults
Basically, every time the CPU requires the intervention of the Virtual Monitor, it will raise a GPF exception. Once you detected the exception is due to a virtual task, you call the monitor's GPF handler.
There, you need to read what's the currently tried instruction (beware prefixes like ES:, REPNE, etc.) and decide how you can emulate it ...
Opcodes to be supported include:
- 0x9C (pushf) and 0x9D (popf)
- 0xCD (int nn) and 0xCF (iret)
- INx/OUTx (0xE4-0xE7, 0x6C-0x6F, 0xEC-0xEF) unless you set io permission bitmap/iopl accordingly
- 0xFA (cli) and 0xFB (sti)
How should i proceed ?
Your monitor will have to perform operations such as faking interrupts, checking instructions and things alike. A good practice would be to write a couple of 'core' functions that will perform simple operations like pushing a value on the virtual task's stack, reading a word at a given segment:offset (like in Real Mode), fetching the currently executed byte from virtual CS:IP, etc.
The list of such methods in Chris Giese's monitor, for instance, include
- unsigned peekb(unsigned seg, unsigned off); which will return a byte located at seg:off
- unsigned peekw(unsigned seg, unsigned off); same for a word
- void pokeb(unsigned seg, unsigned off, unsigned val); which will write a byte to seg:off
- void pokew(unsigned seg, unsigned off, unsigned val); same with a word
- void v86_push16(uregs_t *regs, unsigned value); which will tweak the registers image to push a (16 bits) value on stack.
- void v86_int(uregs_t *regs, unsigned int_num); which actually call an INT in vmode.
There are two intel-providen tricks you may use:
- Set IOPL=3 in your initialization. That will not affect IN/OUT instructions, but instead allows the VM task to toy with IF flags itself. Take care that IRQs and software interrupts will then go directly to the IDT (e.g. the IVT is ignored), so you may need to write code in your pm IRQ handler that will edit the CS:IP values according to that IVT.
- Use "Virtual Mode Extensions", which will allow you to give the TSS a "interrupt redirection bitmap", telling which interrupt should be processed in virtual mode using the IVT and which should be processed in protected mode using the IDT. VME aren't available on QEMU, though.
- http://osdev.berlios.de/v86.html - TimRobinson's vm86-tutorial (dead link, available on archive.org at https://web.archive.org/web/20090719085533/http://osdev.berlios.de/v86.html)
- http://my.execpc.com/~geezer/osd/pmode/v86mm.zip - Chris Giese's YAV86MM
- http://oslib.cvs.sourceforge.net/viewvc/oslib/oslib/xlib/vm86.c?view=markup - OSLib's basic vm86 code (pages-tuning required)