UnnamedOS
vm86.c
Go to the documentation of this file.
1 
19 #include <common.h>
20 #include <tasks/vm86.h>
21 #include <tasks/schedule.h>
22 #include <interrupts/syscall.h>
23 #include <interrupts/isr.h>
24 #include <boot/multiboot.h>
25 #include <mem/gdt.h>
26 #include <mem/vmm.h>
27 #include <string.h>
28 #include <syscall.h>
29 
33 #define CODE_ADDRESS ((void*) 0x500)
34 
36 #define IVT_ADDRESS ((void*) 0)
37 #define OPERAND_SIZE 0x66
38 #define OPCODE_PUSHF 0x9C
39 #define OPCODE_POPF 0x9D
40 #define OPCODE_INT_3 0xCC
41 #define OPCODE_INT 0xCD
42 #define OPCODE_IRET 0xCF
43 #define OPCODE_CLI 0xFA
44 #define OPCODE_STI 0xFB
45 
46 
54 #define CASE_IN(opcode, in_func, operand, inc, type) \
55  case (opcode): \
56  *(type*) &cpu->r.eax = (in_func)(operand); \
57  vm86_increment_eip(cpu, (inc)); \
58  break;
59 
67 #define CASE_OUT(opcode, out_func, operand, inc) \
68  case (opcode): \
69  (out_func)((operand), cpu->r.eax); \
70  vm86_increment_eip(cpu, (inc)); \
71  break;
72 
74 typedef struct {
75  uint16_t offset,
76  segment;
77 } __attribute__((packed)) vm86_farptr_t;
78 
79 static vm86_farptr_t* ivt = IVT_ADDRESS;
80 // symbols defined in vm86_asm.S, only the addresses matter
81 extern const void
85 
91 static vm86_farptr_t vm86_get_farptr(void* addr) {
92  if ((uintptr_t) addr >= MULTIBOOT_LOWER_MEMORY) {
93  println("%4aAddress %08x too large for VM86 mode%a", addr);
94  vm86_farptr_t farptr = {.offset = 0, .segment = 0};
95  return farptr;
96  }
103  uint16_t offset = (uintptr_t) addr & 0xFFFF; // lower word is an offset
104  vm86_farptr_t farptr = {
105  .offset = offset,
106  // everything else belongs to the segment (bit shifting divides by 64KiB)
107  .segment = ((uintptr_t) addr - offset) >> 4
108  };
109  return farptr;
110 }
111 
118 static void vm86_write_farptr(uint16_t* segment, uint16_t* offset,
119  vm86_farptr_t farptr) {
120  *segment = farptr.segment;
121  *offset = farptr.offset;
122 }
123 
129 static void* vm86_get_address(vm86_farptr_t farptr) {
130  // segment * 16 + offset, bit shifting is a little faster
131  return (void*) ((farptr.segment << 4) + farptr.offset);
132 }
133 
145 task_pid_t vm86_create_task(void* code_start, void* code_end,
146  page_directory_t* page_directory, size_t kernel_stack_len,
147  size_t user_stack_len, isr_registers_t* registers) {
149  logln("VM86", "Creating VM86 task with %dKB kernel and %dKB user stack",
150  kernel_stack_len, user_stack_len);
151  task_t* task = vmm_alloc(sizeof(task_t), VMM_KERNEL);
152  task->page_directory = page_directory ? page_directory :
159  vmm_map_range(0, 0, MULTIBOOT_LOWER_MEMORY, VMM_USER | VMM_WRITABLE);
161  uint32_t code_length = (uintptr_t) code_end - (uintptr_t) code_start + 1;
167  memcpy(CODE_ADDRESS, code_start, code_length);
168  task->state = TASK_RUNNING;
169  task->vm86 = 1;
170  task->kernel_stack = vmm_alloc(kernel_stack_len, VMM_KERNEL);
172  task->user_stack = (task_stack_t*) ((uintptr_t) CODE_ADDRESS + code_length);
173  task->kernel_stack_len = kernel_stack_len;
174  task->user_stack_len = user_stack_len;
175  cpu_state_t* cpu = task->cpu = (cpu_state_t*)
176  (task->kernel_stack + kernel_stack_len - 1 - sizeof(cpu_state_t));
177  // the segment registers are overwritten with the vm86_* values when ireting
178  cpu->gs = cpu->fs = cpu->es = cpu->ds = gdt_get_selector(GDT_RING3_DATA_SEG);
179  cpu->r = *registers; // here we pass parameters to the code
180  vm86_farptr_t entry_point_farptr = vm86_get_farptr(CODE_ADDRESS);
181  cpu->eip = entry_point_farptr.offset; // we need to use real mode addressing
182  cpu->cs = entry_point_farptr.segment; // in the form of CS:IP to run the code
183  cpu->eflags.dword = 0;
184  cpu->eflags.bits._if = 1;
185  cpu->eflags.bits.reserved = 1;
186  cpu->eflags.bits.vm = 1; // enter virtual 8086 mode
187  vm86_farptr_t user_stack_farptr = vm86_get_farptr((void*)
188  ((uint32_t) task->user_stack + user_stack_len - 1));
189  cpu->user_esp = user_stack_farptr.offset; // the stack pointer is given
190  cpu->user_ss = user_stack_farptr.segment; // in the form SS:SP in real mode
191  // this is the same segment where code and stack are located:
192  cpu->vm86_es = cpu->vm86_ds = cpu->vm86_fs = cpu->vm86_gs =
193  entry_point_farptr.segment;
195  task_pid_t pid = task_add(task);
196  isr_enable_interrupts(old_interrupts);
197  return pid;
198 }
199 
205 void vm86_call_bios(uint8_t interrupt, isr_registers_t* registers) {
206  uint8_t* opcode = (uint8_t*) &vm86_interrupt_hook; // the interrupt opcode
207  uint8_t* operand = opcode + 1; // the actual interrupt vector
208  if (*opcode != OPCODE_INT) {
209  println("%4aVM86 BIOS handler corrupted%a");
210  return;
211  }
212  *operand = interrupt;
214  0, _4KB, _4KB, registers);
215 }
216 
222 static void vm86_push(cpu_state_t* cpu, uint16_t value) {
223  vm86_farptr_t sssp = {.offset = cpu->user_esp, .segment = cpu->user_ss};
224  uint16_t* new_esp = (uint16_t*) vm86_get_address(sssp) - 1;
225  *new_esp = value;
226  vm86_write_farptr((uint16_t*) &cpu->user_ss, (uint16_t*) &cpu->user_esp,
227  vm86_get_farptr(new_esp));
228 }
229 
235 static uint16_t vm86_pop(cpu_state_t* cpu) {
236  vm86_farptr_t sssp = {.offset = cpu->user_esp, .segment = cpu->user_ss};
237  uint16_t* esp = vm86_get_address(sssp);
238  uint16_t value = *esp;
239  vm86_write_farptr((uint16_t*) &cpu->user_ss, (uint16_t*) &cpu->user_esp,
240  vm86_get_farptr(esp + 1));
241  return value;
242 }
243 
249 static void vm86_increment_eip(cpu_state_t* cpu, size_t inc) {
250  vm86_farptr_t csip = {.offset = cpu->eip, .segment = cpu->cs};
251  vm86_write_farptr(&cpu->cs, (uint16_t*) &cpu->eip,
252  vm86_get_farptr((uint8_t*) vm86_get_address(csip) + inc));
253 }
254 
260 static uint8_t vm86_monitor(cpu_state_t* cpu) {
261  // if we are not in VM86 mode, do not handle this GPF
263  return 0;
264  // First we determine which instruction triggered the GPF. For that we need
265  // to translate real mode (CS:IP) to protected mode (EIP) addresses.
266  vm86_farptr_t csip = {.offset = cpu->eip, .segment = cpu->cs};
267  uint8_t* eip = vm86_get_address(csip);
268  uint16_t opcode = eip[0] == OPERAND_SIZE ? OPERAND_SIZE << 8 | eip[1] : eip[0];
269  // There are multiple instructions that fire an GPF and need to be emulated
270  switch (opcode) { // in different ways.
271  case OPCODE_PUSHF:
272  vm86_push(cpu, cpu->eflags.dword); // push FLAGS onto user stack
273  vm86_increment_eip(cpu, 1);
274  break;
275  case OPCODE_POPF:
276  vm86_pop(cpu); // pop FLAGS, for simplicity we ignore the value
277  vm86_increment_eip(cpu, 1);
278  break;
279  case OPCODE_INT_3:
280  println("BIOS call returned EAX=%08x, EBX=%08x, ECX=%08x, EDX=%08x",
281  cpu->r.eax, cpu->r.ebx, cpu->r.ecx, cpu->r.edx);
282  sys_exit(0); // exit the VM86 task (with a breakpoint exception)
283  break;
284  case OPCODE_INT:
285  logln("VM86", "Emulating INT %02x", eip[1]);
286  // Here the VM86 code called a BIOS function, so we set up the USER
287  // stack how a 8086 CPU would do it: push FLAGS, CS and IP. These
288  // are the values used when the BIOS iret's, so CS:IP points to the
289  // next instruction after the INT.
290  csip = vm86_get_farptr(eip + 2); // = EIP + OPCODE_INT + OPERAND
291  vm86_push(cpu, cpu->eflags.dword); // push FLAGS (16-bit!)
292  vm86_push(cpu, csip.segment); // push CS (next instruction)
293  vm86_push(cpu, csip.offset); // push IP
294  // this is where we want to iret to: the actual BIOS code
295  vm86_write_farptr(&cpu->cs, (uint16_t*) &cpu->eip, ivt[eip[1]]);
296  break;
297  case OPCODE_IRET:
298  logln("VM86", "Emulating IRET");
299  // This is executed when a BIOS call has finished. We reverse the
300  // pushes we did above.
301  cpu->eip = vm86_pop(cpu); // pop IP
302  cpu->cs = vm86_pop(cpu); // pop CS
303  vm86_pop(cpu); // pop FLAGS
304  break;
305 #pragma GCC diagnostic push
306 #pragma GCC diagnostic ignored "-Wstrict-aliasing"
307  // Different flavours of port mapped I/O. We could use a TSS I/O map
308  CASE_IN (0xE4, inb, eip[1], 2, uint8_t); // to manage this, for now
309  CASE_IN (0xE5, inw, eip[1], 2, uint16_t); // we just allow any I/O
310  CASE_IN (0x66E5, inl, eip[2], 3, uint32_t); // operation.
311  CASE_OUT(0xE6, outb, eip[1], 2);
312  CASE_OUT(0xE7, outw, eip[1], 2);
313  CASE_OUT(0x66E7, outl, eip[2], 3);
314  CASE_IN (0xEC, inb, cpu->r.edx, 1, uint8_t);
315  CASE_IN (0xED, inw, cpu->r.edx, 1, uint16_t);
316  CASE_IN (0x66ED, inl, cpu->r.edx, 2, uint32_t);
317  CASE_OUT(0xEE, outb, cpu->r.edx, 1);
318  CASE_OUT(0xEF, outw, cpu->r.edx, 1);
319  CASE_OUT(0x66EF, outl, cpu->r.edx, 2);
320 #pragma GCC diagnostic pop
321  case OPCODE_CLI: // we pretend to enable / disable interrupts,
322  case OPCODE_STI: // should work most times
323  vm86_increment_eip(cpu, 1);
324  break;
325  default:
326  panic("VM86 opcode %02x unhandled (CS:IP=%04x:%04x)",
327  opcode, cpu->cs, cpu->eip);
328  }
329  return 1;
330 }
331 
338  if (!vm86_monitor(cpu))
339  panic("%4aEX%02x (EIP=%08x)", cpu->intr, cpu->eip);
340  return cpu;
341 }
342 
344 void vm86_init() {
346 }
347 
#define GDT_RING3_DATA_SEG
user data segment index
Definition: gdt.h:16
#define _4KB
4KB are 4096 bytes, often used for stacks
Definition: task.h:14
#define CASE_IN(opcode, in_func, operand, inc, type)
Emulates an IN instruction.
Definition: vm86.c:54
page_directory_t * vmm_create_page_directory()
Creates an empty page directory.
Definition: vmm.c:72
uint8_t task_get_vm86(task_pid_t pid)
Returns whether a task is a VM86 task.
Definition: task.c:267
#define OPERAND_SIZE
opcode for overriding operand size
Definition: vm86.c:37
uint8_t isr_enable_interrupts(uint8_t enable)
Enables or disables interrupts.
Definition: isr.c:42
general purpose registers
Definition: isr.h:42
void vmm_enable_domain_check(uint8_t enable)
Enables or disables domain checking.
Definition: vmm.c:539
A real mode far pointer.
Definition: vm86.c:74
uint16_t gs
GS segment register.
Definition: isr.h:60
task_state_t state
whether the task is running or stopped
Definition: task.h:26
task_pid_t schedule_get_current_task()
Returns the current task&#39;s PID.
Definition: schedule.c:74
task_stack_t * kernel_stack
stack for handling interrupts
Definition: task.h:28
void vm86_init()
Initializes VM86 mode.
Definition: vm86.c:344
static vm86_farptr_t vm86_get_farptr(void *addr)
Translate an address into a far pointer.
Definition: vm86.c:91
uint32_t edx
data register
Definition: isr.h:43
static page_directory_t * page_directory
the current page directory
Definition: vmm.c:44
const void vm86_interrupt_hook
location of INT in the BIOS call code
#define OPCODE_PUSHF
PUSHF triggers a GPF inside VM86 mode.
Definition: vm86.c:38
void vmm_modified_page_directory()
Ends a page directory modification.
Definition: vmm.c:164
The CPU&#39;s state when an interrupt occurs.
Definition: isr.h:58
uint16_t vm86_ds
DS register (only pushed and popped in VM86 mode)
Definition: isr.h:77
void * vmm_alloc(size_t len, vmm_flags_t flags)
Marks some page(s) as used and maps them somewhere into memory.
Definition: vmm.c:511
static uint16_t vm86_pop(cpu_state_t *cpu)
Emulates a POP instruction.
Definition: vm86.c:235
static void vm86_write_farptr(uint16_t *segment, uint16_t *offset, vm86_farptr_t farptr)
Writes a far pointer into another location.
Definition: vm86.c:118
void vmm_map_range(void *vaddr, void *paddr, size_t len, vmm_flags_t flags)
Maps the given page(s) into memory.
Definition: vmm.c:341
static void vm86_push(cpu_state_t *cpu, uint16_t value)
Emulates a PUSH instruction.
Definition: vm86.c:222
task_pid_t task_add(task_t *task)
Adds a new task to the task list and associates a PID.
Definition: task.c:46
uint16_t vm86_gs
GS register (only pushed and popped in VM86 mode)
Definition: isr.h:77
cpu_state_t * cpu
saved CPU state when entering/leaving interrupts
Definition: task.h:32
size_t kernel_stack_len
kernel stack length
Definition: task.h:30
static vm86_farptr_t * ivt
IVT needed to do BIOS calls.
Definition: vm86.c:79
size_t user_stack_len
user stack length
Definition: task.h:30
static uint8_t vm86_monitor(cpu_state_t *cpu)
Monitors a VM86 task by emulating sensitive instructions.
Definition: vm86.c:260
static uint8_t old_interrupts
for temporary modifications
Definition: vmm.c:46
uint16_t offset
offset inside the segment
Definition: vm86.c:75
uint32_t user_ss
stack segment (only pushed and popped in user space)
Definition: isr.h:74
#define IVT_ADDRESS
The real mode IVT (= Interrupt Vector Table) lies at the start of memory.
Definition: vm86.c:36
#define CASE_OUT(opcode, out_func, operand, inc)
Emulates an OUT instruction.
Definition: vm86.c:67
uint8_t task_stack_t
stacks are measured in bytes
Definition: task.h:16
#define OPCODE_STI
STI triggers a GPF inside VM86 mode.
Definition: vm86.c:44
uint8_t _if
interrupt flag
Definition: isr.h:19
page_directory_t * page_directory
this task&#39;s virtual memory map
Definition: task.h:27
uint32_t eip
the instruction to return to after the interrupt
Definition: isr.h:68
uint32_t task_pid_t
unique process ID
Definition: task.h:17
#define ISR_EXCEPTION(ex)
the interrupt vector for an exception
Definition: isr.h:12
internal representation of a task
Definition: task.h:25
#define CODE_ADDRESS
Where the assembly code will be located.
Definition: vm86.c:33
uint16_t gdt_get_selector(size_t entry)
Returns a selector ready to be loaded in a segment register.
Definition: gdt.c:103
task_pid_t vm86_create_task(void *code_start, void *code_end, page_directory_t *page_directory, size_t kernel_stack_len, size_t user_stack_len, isr_registers_t *registers)
Creates a VM86 task.
Definition: vm86.c:145
void isr_register_handler(size_t intr, isr_handler_t handler)
Registers a handler to call whenever a given interrupt is fired.
Definition: isr.c:66
uint16_t es
ES segment register.
Definition: isr.h:60
uint8_t vm86
whether this task is running in Virtual 8086 mode
Definition: task.h:34
uint32_t dword
useful for casting
Definition: isr.h:38
static void vm86_increment_eip(cpu_state_t *cpu, size_t inc)
Emulates the completion of an instruction.
Definition: vm86.c:249
uint8_t vm
virtual 8086 mode
Definition: isr.h:19
uint16_t vm86_es
ES register (only pushed and popped in VM86 mode)
Definition: isr.h:77
task_stack_t * user_stack
stack for the actual task&#39;s code
Definition: task.h:29
static void * vm86_get_address(vm86_farptr_t farptr)
Translate a far pointer into an address.
Definition: vm86.c:129
An entry in a page directory.
Definition: vmm.h:25
uint16_t fs
FS segment register.
Definition: isr.h:60
uint8_t reserved
always one
Definition: isr.h:19
#define OPCODE_IRET
IRET triggers a GPF inside VM86 mode.
Definition: vm86.c:42
void vmm_modify_page_directory(page_directory_t *new_directory)
Loads a page directory for temporary modification.
Definition: vmm.c:149
struct isr_eflags_t::@8 bits
bit field
uint32_t eax
accumulator register
Definition: isr.h:43
uint16_t cs
the code segment to return to after the interrupt
Definition: isr.h:71
uint16_t isr_eflags_t eflags
the EFLAGS register before the interrupt was fired
Definition: isr.h:71
uint16_t vm86_fs
FS register (only pushed and popped in VM86 mode)
Definition: isr.h:77
#define OPCODE_INT_3
INT $3 triggers a GPF inside VM86 mode.
Definition: vm86.c:40
uint32_t ebx
base register
Definition: isr.h:43
#define OPCODE_CLI
CLI triggers a GPF inside VM86 mode.
Definition: vm86.c:43
void vm86_call_bios(uint8_t interrupt, isr_registers_t *registers)
Calls a BIOS interrupt.
Definition: vm86.c:205
static cpu_state_t * vm86_handle_gpf(cpu_state_t *cpu)
Handles general protection faults.
Definition: vm86.c:337
uint16_t segment
blocks of 16 byte
Definition: vm86.c:75
uint32_t user_esp
stack pointer (only pushed and popped in user space)
Definition: isr.h:74
const void vm86_call_bios_end
end of the BIOS call code
uint16_t ds
DS segment register.
Definition: isr.h:60
uint32_t ecx
counter register
Definition: isr.h:43
const void vm86_call_bios_start
start of the BIOS call code
uint32_t intr
the interrupt vector of the fired interrupt
Definition: isr.h:68
#define OPCODE_POPF
POPF triggers a GPF inside VM86 mode.
Definition: vm86.c:39
#define OPCODE_INT
INT triggers a GPF inside VM86 mode.
Definition: vm86.c:41
uint16_t isr_registers_t r
The general purpose registers.
Definition: isr.h:60