3 ; Test program: Proof-of-concept minimalist virtual 8086 "monitor"
4 ; (C) 2010-2012 Jonathan Campbell.
5 ; Hackipedia DOS library.
7 ; This code is licensed under the LGPL.
8 ; <insert LGPL legal text here>
10 ; MODE: 16-bit real mode MS-DOS .COM executable
11 ; *THIS CODE IS OBSOLETE*
12 ; Assumes DS == ES == SS
15 ; - This works... for the most part.
16 ; - Somehow this works even with DOSBox's funky ROM-based interrupt emulation
17 ; - The emulation provided is sufficient for real-mode exceptions including INT 3h debug and
18 ; INT 1h trace. It also handles the correct exception to permit v86 programs to use the
21 ; - Privileged instructions like mov cr0,<reg> trigger an exception and this program makes no
22 ; attempt to emulate those instructions.
23 ; - This code makes no attempt to emulate the LDT manipulation that most BIOS implementations
24 ; apparently like to do when handling INT 15H extended memory copy. Programs that use extended
25 ; memory via HIMEM.SYS or via INT 15H will crash.
26 ; - For reasons unknown to me, running this under Windows 95 pure DOS mode is crashy. It will run
27 ; for awhile but eventually, things will hang. Under QEMU, running another program or a 3rd
28 ; will trigger a sudden reset, which mirrors behavior seen on an actual Pentium system. For
29 ; other unknown reasons, Bochs and DOSBox run this code just fine.
30 ; - Whatever the BIOS does in response to CTRL+ALT+DEL it doesn't work well when we are active.
32 ; This code manages virtual 8086 mode in a suboptimal way. A better implementation is provided in
35 ; FIXME: Okay now this is crashing. Why?
37 ; Standard selectors in protected mode
39 CODE16_SEL equ (1 << 3)
40 DATA16_SEL equ (2 << 3)
41 CODE32_SEL equ (3 << 3)
42 DATA32_SEL equ (4 << 3)
43 FLAT16_SEL equ (5 << 3)
44 FLAT32_SEL equ (6 << 3)
47 TSS_VM86_SEL equ (9 << 3)
50 ; We reprogram the PIC to direct IRQ 0-15 to this base interrupt
54 ; Extensible virtual 8086 mode kernel for DOS
55 ; (C) 2011 Jonathan Campbell
62 ; ============= ENTRY POINT
64 mov word [my_realmode_seg],ax
66 mov sp,stack_init ; SP is normally at 0xFFF0 so move it back down
67 mov word [himem_sys_buffer_handle],0
68 mov byte [user_req_unload],0
69 mov byte [user_req_iopl],3
70 mov byte [irq_pending],0
75 ; ============= CPU DETECT
86 ; 286 test: EFLAGS will always have bits 12-15 clear
97 cpu_is_386_not: mov ax,1
101 ; ============= EXIT WITH MESSAGE ($-terminated string at DS:DX)
102 _exit_with_msg: mov ah,9
103 int 21h ; fall through to _exit
107 cmp word [himem_sys_buffer_handle],0 ; if there is a handle to free, then do it
109 mov ah,0Dh ; HIMEM.SYS function 0Dh unlock memory block
110 mov dx,word [himem_sys_buffer_handle]
111 call far word [himem_sys_entry]
112 mov ah,0Ah ; HIMEM.SYS function 0Ah free memory block
113 mov dx,word [himem_sys_buffer_handle]
114 call far word [himem_sys_entry]
115 .no_handle: mov ax,4C00h
118 ; ============= PROGRAM STARTS HERE
119 _entry: call parse_argv
123 .argv_ok: call cpu_is_386 ; CHECK: 386 or higher
125 mov dx,str_require_386
127 .is386: cmp byte [user_req_unload],0; CHECK: Did user request that we unload?
129 jmp unload_this_program
130 .not_unload: smsw ax ; CHECK: Virtual 8086 mode not already enabled
133 mov dx,str_v86_detected
135 .not_v86: cmp dword [himem_sys_buffer_size],64*1024 ; CHECK: buffer size is 64KB or larger
136 jge .buffer_size_large_enough
137 mov dx,str_buffer_too_small
139 .buffer_size_large_enough:
140 cmp dword [himem_sys_buffer_size],16*1024*1024
141 jle .buffer_size_small_enough
142 mov dx,str_buffer_too_large
144 .buffer_size_small_enough:
145 mov ax,4300h ; CHECK: HIMEM.SYS is present
149 mov dx,str_need_himem_sys
151 .yes_himem_sys: mov ax,4310h ; Get HIMEM.SYS entry point (cannot fail)
153 mov word [himem_sys_entry],bx
154 mov word [himem_sys_entry+2],es
155 mov ah,5h ; HIMEM.SYS Local Enable A20
156 call far word [himem_sys_entry]
159 mov dx,str_himem_a20_error
161 .yes_himem_a20: mov ah,09h ; HIMEM.SYS allocate block
162 cli ; <- in case BIOS interrupts do not save upper 16 bits
163 mov edx,[himem_sys_buffer_size]
165 shr edx,10 ; EDX = (X BYTES+1023)/1024 KB
166 call far word [himem_sys_entry]
169 mov dx,str_himem_alloc_err
171 .yes_himem_buf: mov word [himem_sys_buffer_handle],dx ; store memory handle
172 mov ah,0Ch ; HIMEM.SYS lock memory block
173 call far word [himem_sys_entry] ; NOTE: DX = memory handle (still)
176 mov dx,str_himem_lock_err
178 .yes_himem_lock:mov word [himem_sys_buffer_phys],bx ; store DX:BX physical memory address
179 mov word [himem_sys_buffer_phys+2],dx
181 ; choose where things go within the buffer
182 ; = 104 bytes for main TSS
183 mov eax,[himem_sys_buffer_phys]
184 mov [tss_phys_base],eax
186 ; = 8192+104 bytes for VM86 TSS
187 mov [tss_vm86_phys_base],eax
189 ; = 4096 for kernel 32 stack
190 mov [kern32_stack_base],eax
193 mov [kern32_stack_top],ebx
194 ; = store it for future allocation
195 mov [buffer_alloc],eax
197 ; PRINT "BUFFER AT: " + *((DWORD*)himem_sys_buffer_phys) + "\n"
201 mov eax,[himem_sys_buffer_phys]
203 call eax_to_hex_16_dos
208 mov eax,[himem_sys_buffer_phys]
209 add eax,[himem_sys_buffer_size]
211 mov byte [scratch_str],'-'
213 call eax_to_hex_16_dos
224 mov dword [my_phys_base],eax
226 ; clear the IDT and GDT
234 ; prepare the IDTR and GDTR.
235 ; real mode versions: limit=0xFFFF base=0
238 mov word [idtr_real],ax
239 mov word [gdtr_real],ax
241 mov dword [idtr_real+2],eax
242 mov dword [gdtr_real+2],eax
243 ; protected mode GDTR limit=MAX_SEL-1 base=(code segment)+var
244 mov word [gdtr_pmode],MAX_SEL - 1
245 mov word [idtr_pmode],(256 << 3) - 1
246 mov eax,[my_phys_base]
248 mov dword [gdtr_pmode+2],eax
249 mov eax,[my_phys_base]
251 mov dword [idtr_pmode+2],eax
255 lea di,[gdt+CODE16_SEL]
256 ; Code selector (CODE_16SEL)
257 dec ax ; 0x0000 - 1 = 0xFFFF
259 mov ax,[my_phys_base]
261 mov al,[my_phys_base+2]
263 stosw ; BASE[23:16] access byte=executable readable
265 mov ah,[my_phys_base+3] ; LIMIT[19:16] flags=0 BASE[31:24]
267 ; Data selector (DATA16_SEL)
271 mov ax,[my_phys_base]
273 mov al,[my_phys_base+2]
275 stosw ; BASE[23:16] access byte=data writeable
277 mov ah,[my_phys_base+3] ; LIMIT[19:16] flags=0 BASE[31:24]
279 ; Code selector (CODE_32SEL)
280 dec ax ; 0x0000 - 1 = 0xFFFF
282 mov ax,[my_phys_base]
284 mov al,[my_phys_base+2]
286 stosw ; BASE[23:16] access byte=executable readable
288 mov ah,[my_phys_base+3] ; LIMIT[19:16] flags=0 BASE[31:24]
290 ; Data selector (DATA32_SEL)
294 mov ax,[my_phys_base]
296 mov al,[my_phys_base+2]
298 stosw ; BASE[23:16] access byte=data writeable
300 mov ah,[my_phys_base+3] ; LIMIT[19:16] flags=0 BASE[31:24]
302 ; Data selector (FLAT16_SEL)
309 stosw ; BASE[23:16] access byte=data writeable
313 ; Data selector (FLAT32_SEL)
320 stosw ; BASE[23:16] access byte=data writeable
324 ; LDT selector (LDT_SEL)
325 mov ax,7 ; I have no use for the LDT
327 mov ax,[my_phys_base]
329 mov al,[my_phys_base+2]
331 stosw ; BASE[23:16] access byte=data writeable LDT type 2
333 mov ah,[my_phys_base+3] ; LIMIT[19:16] flags=0 BASE[31:24]
335 ; TSS selector (TSS_SEL)
338 mov ax,[tss_phys_base]
340 mov al,[tss_phys_base+2]
342 stosw ; BASE[23:16] access byte=data writeable non-busy TSS type 9
344 mov ah,[tss_phys_base+3] ; LIMIT[19:16] flags=0 BASE[31:24]
346 ; TSS selector (TSS_VM86_SEL)
349 mov ax,[tss_vm86_phys_base]
351 mov al,[tss_vm86_phys_base+2]
353 stosw ; BASE[23:16] access byte=data writeable non-busy TSS type 9
355 mov ah,[tss_vm86_phys_base+3] ; LIMIT[19:16] flags=0 BASE[31:24]
358 ; prepare the CPU registers
362 ; enter protected mode
365 jmp CODE16_SEL:pmode16_entry
366 pmode16_entry: mov ax,DATA16_SEL
382 ; now enter 32-bit protected mode
383 jmp CODE32_SEL:pmode32_entry
385 pmode32_entry: mov ax,DATA32_SEL
393 ; at this point: we are in 32-bit protected mode!
395 ; ============= setup the TSS representing our task (for when we return)
397 mov edi,[tss_phys_base]
398 sub edi,[my_phys_base]
400 xor eax,eax ; TSS+0x00 = no backlink
402 mov eax,[kern32_stack_top] ; TSS+0x04 = ESP for CPL0
403 sub eax,[my_phys_base]
405 mov eax,DATA32_SEL ; TSS+0x08 = SS for CPL0
407 mov eax,[kern32_stack_top] ; TSS+0x0C = ESP for CPL1
408 sub eax,[my_phys_base]
410 mov eax,DATA32_SEL ; TSS+0x10 = SS for CPL1
412 mov eax,[kern32_stack_top] ; TSS+0x14 = ESP for CPL2
413 sub eax,[my_phys_base]
415 mov eax,DATA32_SEL ; TSS+0x18 = SS for CPL2
417 xor eax,eax ; TSS+0x1C = CR3
419 mov eax,vm86_entry ; TSS+0x20 = EIP
421 mov eax,0x00000002 ; TSS+0x24 = EFLAGS VM=0
423 xor eax,eax ; TSS+0x28 = EAX
425 xor eax,eax ; TSS+0x2C = ECX
427 xor eax,eax ; TSS+0x30 = EDX
429 xor eax,eax ; TSS+0x34 = EBX
431 mov eax,stack_init_vm86 ; TSS+0x38 = ESP
433 xor eax,eax ; TSS+0x3C = EBP
435 xor eax,eax ; TSS+0x40 = ESI
437 xor eax,eax ; TSS+0x44 = EDI
439 mov ax,DATA32_SEL ; TSS+0x48 = ES
441 mov ax,CODE32_SEL ; TSS+0x4C = CS
443 mov ax,DATA32_SEL ; TSS+0x50 = SS
445 mov ax,DATA32_SEL ; TSS+0x54 = DS
447 mov ax,DATA32_SEL ; TSS+0x58 = FS
449 mov ax,DATA32_SEL ; TSS+0x5C = GS
451 xor eax,eax ; TSS+0x60 = LDTR
453 mov eax,(104 << 16) ; TSS+0x64 = I/O map base
456 ; ============= setup the TSS representing the virtual 8086 mode task
458 mov edi,[tss_vm86_phys_base]
459 sub edi,[my_phys_base]
461 xor eax,eax ; TSS+0x00 = no backlink
463 mov eax,[kern32_stack_top] ; TSS+0x04 = ESP for CPL0
464 sub eax,[my_phys_base]
466 mov eax,DATA32_SEL ; TSS+0x08 = SS for CPL0
468 mov eax,[kern32_stack_top] ; TSS+0x0C = ESP for CPL1
469 sub eax,[my_phys_base]
471 mov eax,DATA32_SEL ; TSS+0x10 = SS for CPL1
473 mov eax,[kern32_stack_top] ; TSS+0x14 = ESP for CPL2
474 sub eax,[my_phys_base]
476 mov eax,DATA32_SEL ; TSS+0x18 = SS for CPL2
478 xor eax,eax ; TSS+0x1C = CR3
480 mov eax,vm86_entry ; TSS+0x20 = EIP
482 mov eax,0x00020202 ; TSS+0x24 = EFLAGS VM=1 IOPL=N IF=1
483 movzx ebx,byte [user_req_iopl]
486 or eax,ebx ; EFLAGS |= user_req_iopl << 12
488 xor eax,eax ; TSS+0x28 = EAX
490 xor eax,eax ; TSS+0x2C = ECX
492 xor eax,eax ; TSS+0x30 = EDX
494 xor eax,eax ; TSS+0x34 = EBX
496 mov eax,stack_init ; TSS+0x38 = ESP
498 xor eax,eax ; TSS+0x3C = EBP
500 xor eax,eax ; TSS+0x40 = ESI
502 xor eax,eax ; TSS+0x44 = EDI
504 mov ax,[my_realmode_seg] ; TSS+0x48 = ES
506 mov ax,[my_realmode_seg] ; TSS+0x4C = CS
508 mov ax,[my_realmode_seg] ; TSS+0x50 = SS
510 mov ax,[my_realmode_seg] ; TSS+0x54 = DS
512 mov ax,[my_realmode_seg] ; TSS+0x58 = FS
514 mov ax,[my_realmode_seg] ; TSS+0x5C = GS
516 xor eax,eax ; TSS+0x60 = LDTR
518 mov eax,(104 << 16) ; TSS+0x64 = I/O map base
521 mov ecx,8192 >> 2 ; TSS+0x68 = I/O permission map (pre-set to all open)
529 .idtdef: mov ax,fault_no_int ; no interrupt assigned procedure
533 mov ax,0x8E00 ; DPL=3
539 mov esi,fault_routines
546 mov ax,0x8E00 ; DPL=3
555 mov edi,idt + (IRQ_BASE_INT*8)
560 mov ax,0x8E00 ; you must set DPL=3
566 ; next we need to reprogram the PIC so that IRQ 0-7 do not conflict with the CPU exceptions.
567 ; note for stability we only reprogram first PIC, since we do not relocate the 2nd PIC.
568 mov al,0x10 ; ICW1 A0=0
570 mov al,IRQ_BASE_INT ; ICW2 A0=1
572 mov al,0x04 ; ICW3 A0=1 slave on IRQ 2
575 ; jump into virtual 8086 mode
578 ; =============== IRQ handler code
580 int_rm_map: db 0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F
581 db 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77
583 irq_priority: db 0,1,8,9,10,11,12,13,14,15,2,3,4,5,6,7
584 ; ^ 16 entries, reflecting IRQ 8-15 -> IRQ 2 (slave PIC) and IRQ 0-7 (master PIC) cascade
586 ; EAX = IRQ that fired
597 irq_general: test dword [esp+12],0x20000 ; did the interrupt happen while in v86 mode?
599 ; this happened while NOT in v86 mode. We should still reflect it back to v86 mode.
600 ; ----------------------TODO--------------------
601 call fault_collect_regs
604 jmp fault_jmp_unhandled
605 ; ----------------------------------------------
606 ; CPU was in v86 mode. Modify stack pointer to reflect the interrupt.
607 .reflect_v86: test dword [esp+12],0x200 ; are interrupts enabled in the vm?
608 jz .reflect_v86_pending ; if not, then we need to note it and reflect later
611 mov bx,FLAT32_SEL ; NTS: Don't worry about saving ES. The CPU saved
612 mov ds,bx ; it as part of the v86 -> interrupt transition.
615 mov bl,[cs:int_rm_map+eax] ; IRQ -> interrupt
616 mov ebx,[ebx*4] ; interrupt -> realmode vector
617 mov ax,[esp+20+8] ; fetch SS
618 shl eax,4 ; EAX = SS*16
619 mov cx,[esp+16+8] ; fetch SP
621 add eax,ecx ; EAX = SS*16 + SP
622 mov [esp+16+8],cx ; store modified SP back
623 ; EBX = realmode interrupt vector
624 ; EAX = stack pointer (physical mem addr)
625 mov cx,[esp+4+8] ; fetch EIP
626 mov word [eax+0],cx ; SS:SP = offset
627 mov cx,[esp+8+8] ; fetch CS
628 mov word [eax+2],cx ; SS:SP+2 = segment
629 mov cx,[esp+12+8] ; fetch FLAGS
630 mov word [eax+4],cx ; SS:SP+4 = flags
631 mov [esp+4+8],bx ; overwrite IP = offset of vector
633 mov [esp+8+8],bx ; overwrite CS = segment of vector
642 ; v86 mode, but interrupts are disabled
643 .reflect_v86_pending:
650 shl eax,cl ; EAX = 1 << IRQ
651 or word [irq_pending],ax ; irq_pending |= 1 << IRQ
721 ; =============== GENERAL PURPOSE "NO INTERRUPT ASSIGNED" HANDLER
724 fault_x86_vector:db 0
725 ; =============== REFLECT EXCEPTION TO REAL MODE
730 mov bx,FLAT32_SEL ; NTS: Don't worry about saving ES. The CPU saved
731 mov ds,bx ; it as part of the v86 -> interrupt transition.
734 mov bl,[cs:fault_x86_vector]; what interrupt is involved?
735 mov ebx,[ebx*4] ; interrupt -> realmode vector
736 mov ax,[esp+20+8] ; fetch SS
737 shl eax,4 ; EAX = SS*16
738 mov cx,[esp+16+8] ; fetch SP
740 add eax,ecx ; EAX = SS*16 + SP
741 mov [esp+16+8],cx ; store modified SP back
742 ; EBX = realmode interrupt vector
743 ; EAX = stack pointer (physical mem addr)
744 mov cx,[esp+4+8] ; fetch EIP
745 mov word [eax+0],cx ; SS:SP = offset
746 mov cx,[esp+8+8] ; fetch CS
747 mov word [eax+2],cx ; SS:SP+2 = segment
748 mov cx,[esp+12+8] ; fetch FLAGS
749 mov word [eax+4],cx ; SS:SP+4 = flags
750 mov [esp+4+8],bx ; overwrite IP = offset of vector
752 mov [esp+8+8],bx ; overwrite CS = segment of vector
753 ; if this is INT 0x01 we also need to clear the TF bit
754 cmp byte [cs:fault_x86_vector],1
756 and word [esp+12+8],~0x100 ; clear TF
767 ; =============== FAULT HANDLER CODE
768 fault_0x00: push dword 0 ; ERROR CODE
769 call fault_collect_regs
771 mov esi,str_fault_0x00
772 jmp fault_jmp_unhandled
774 fault_0x01: test dword [esp+8],0x20000 ; did it happen from within v86 mode?
776 push dword 0 ; ERROR CODE
777 call fault_collect_regs
779 mov esi,str_fault_0x01
780 jmp fault_jmp_unhandled
781 .reflect_v86: mov byte [ss:fault_x86_vector],0x01 ; reflect to INT 0x01
782 jmp fault_v86_reflect
784 fault_0x02: push dword 0 ; ERROR CODE
785 call fault_collect_regs
787 mov esi,str_fault_0x02
788 jmp fault_jmp_unhandled
790 fault_0x03: test dword [esp+8],0x20000 ; did it happen from within v86 mode?
792 push dword 0 ; ERROR CODE
793 call fault_collect_regs
795 mov esi,str_fault_0x03
796 jmp fault_jmp_unhandled
797 .reflect_v86: mov byte [ss:fault_x86_vector],0x03 ; reflect to INT 0x03
798 jmp fault_v86_reflect
800 fault_0x04: push dword 0 ; ERROR CODE
801 call fault_collect_regs
803 mov esi,str_fault_0x04
804 jmp fault_jmp_unhandled
806 fault_0x05: push dword 0 ; ERROR CODE
807 call fault_collect_regs
809 mov esi,str_fault_0x05
810 jmp fault_jmp_unhandled
812 fault_0x06: push dword 0 ; ERROR CODE
813 call fault_collect_regs
815 mov esi,str_fault_0x06
816 jmp fault_jmp_unhandled
820 test eax,0x08 ; is this a result of CR0.TS being set?
823 ; very likely a real-mode DOS application executed floating point instructions, and
824 ; the task switch into vm86 mode left bit 3 (CR0.TS) set. Clear it and return. This
825 ; is necessary to allow the DOS system to use floating point, even on 486/Pentium and
826 ; higher systems where the FPU is integral to the CPU. Even for simple instructions
830 ; if the exception did NOT involve that bit, then yes, it's something to halt on
831 .not_cr0_ts: push dword 0 ; ERROR CODE
832 call fault_collect_regs
834 mov esi,str_fault_0x07
835 jmp fault_jmp_unhandled
837 fault_0x08: call fault_collect_regs
839 mov esi,str_fault_0x08
840 jmp fault_jmp_unhandled
842 fault_0x09: push dword 0 ; ERROR CODE
843 call fault_collect_regs
845 mov esi,str_fault_0x09
846 jmp fault_jmp_unhandled
848 fault_0x0A: call fault_collect_regs
850 mov esi,str_fault_0x0A
851 jmp fault_jmp_unhandled
853 fault_0x0B: call fault_collect_regs
855 mov esi,str_fault_0x0B
856 jmp fault_jmp_unhandled
858 fault_0x0C: call fault_collect_regs
860 mov esi,str_fault_0x0C
861 jmp fault_jmp_unhandled
863 fault_0x0E: call fault_collect_regs
865 mov esi,str_fault_0x0E
866 jmp fault_jmp_unhandled
868 fault_0x0F: push dword 0 ; ERROR CODE
869 call fault_collect_regs
871 mov esi,str_fault_0x0F
872 jmp fault_jmp_unhandled
874 fault_0x10: push dword 0 ; ERROR CODE
875 call fault_collect_regs
877 mov esi,str_fault_0x10
878 jmp fault_jmp_unhandled
880 fault_0x11: push dword 0 ; ERROR CODE
881 call fault_collect_regs
883 mov esi,str_fault_0x11
884 jmp fault_jmp_unhandled
886 fault_0x12: push dword 0 ; ERROR CODE
887 call fault_collect_regs
889 mov esi,str_fault_0x12
890 jmp fault_jmp_unhandled
892 fault_0x13: push dword 0 ; ERROR CODE
893 call fault_collect_regs
895 mov esi,str_fault_0x13
896 jmp fault_jmp_unhandled
898 fault_0x14: call fault_collect_regs
900 jmp fault_jmp_unhandled_unknown
902 fault_0x15: call fault_collect_regs
904 jmp fault_jmp_unhandled_unknown
906 fault_0x16: call fault_collect_regs
908 jmp fault_jmp_unhandled_unknown
910 fault_0x17: call fault_collect_regs
912 jmp fault_jmp_unhandled_unknown
914 fault_0x18: call fault_collect_regs
916 jmp fault_jmp_unhandled_unknown
918 fault_0x19: call fault_collect_regs
920 jmp fault_jmp_unhandled_unknown
922 fault_0x1A: call fault_collect_regs
924 jmp fault_jmp_unhandled_unknown
926 fault_0x1B: call fault_collect_regs
928 jmp fault_jmp_unhandled_unknown
930 fault_0x1C: call fault_collect_regs
932 jmp fault_jmp_unhandled_unknown
934 fault_0x1D: call fault_collect_regs
936 jmp fault_jmp_unhandled_unknown
938 fault_0x1E: call fault_collect_regs
940 jmp fault_jmp_unhandled_unknown
942 fault_0x1F: call fault_collect_regs
944 jmp fault_jmp_unhandled_unknown
946 fault_jmp_unhandled_unknown:
947 mov esi,str_fault_unknown
948 jmp fault_jmp_unhandled
950 ; ============= EXCEPTION HANDLER: INT 0x0D GENERAL PROTECTION FAULT
951 ; If caused by v8086 mode:
952 ; [ESP+0] = error code
962 ; Else, only the error code, EIP, CS, EFLAGS fields are present.
963 ; If not from ring 0, then ESP, SS are as well.
964 fault_0x0D: test dword [esp+12],0x20000 ; [ESP+12] = EFLAGS. Is bit 17 (VM) set?
967 ; at this point, we know this is the processor trapping CLI/STI or anything that a v86 monitor needs to know.
968 ; so the next thing we do is examine the opcode at CS:IP to determine what the code is trying to do, and how
969 ; to emulate it. Note the CS:IP off stack are REAL MODE addresses.
977 mov ax,[esp+0x20+8] ; CS ON STACK
979 add bx,[esp+0x20+4] ; EIP ON STACK
981 sub eax,[my_phys_base] ; REMEMBER our data segment is relative to the COM.
982 ; ALSO KNOW most x86 processors will wrap addresses like
983 ; 0xFFFFF000 back around to 0 (32-bit overflow) with nonzero
986 mov eax,[eax] ; fetch 4 bytes at CS:IP
987 mov [v86_raw_opcode],eax ; store for reference
993 cmp al,0xF4 ; HLT? (apparently, yes, that causes a GPF from v86 mode)
995 cmp al,0xCD ; INT X (AH=interrupt)
1001 cmp al,0x9C ; PUSHF 16-bit
1003 cmp al,0x9D ; POPF 16-bit
1005 cmp ax,0x9C66 ; PUSHFD 32-bit
1007 cmp ax,0x9D66 ; POPFD 32-bit
1011 .v86_complete_and_check_pending_irq: ; <----------- COMPLETION, PLUS CHECK IF INTERRUPTS ENABLED, PENDING IRQs
1012 test word [esp+0x20+12],0x200 ; are interrupts enabled (IF=1)
1014 ; interrupts enabled, are there pending IRQs?
1015 cmp word [irq_pending],0
1017 ; for each pending IRQ, stuff the stack with an interrupt frame.
1018 ; this must be done in the order the PIC would do based on IRQ priority.
1021 mov esi,irq_priority
1022 .v86_complete_and_check_pending_irq_scan:
1027 mov cl,al ; <- NTS: bits 8-31 should be zero because we inited ECX == 16
1030 test word [irq_pending],bx ; if (irq_pending & (1 << AL)) ...
1031 jnz .v86_complete_and_check_pending_irq_found
1032 loop .v86_complete_and_check_pending_irq_scan
1033 ; FALL THROUGH TO COMPLETION
1034 .v86_complete: popad
1035 .v86_complete_no_popad:
1036 add esp,4 ; dump error code (usually zero)
1039 ; we found a pending IRQ. EBX = 1 << IRQ, EAX = IRQ
1040 .v86_complete_and_check_pending_irq_found:
1042 xor word [irq_pending],bx ; clear the bit
1044 movzx ebx,al ; EBX = interrupt number
1046 mov es,ax ; we'll need flat mode for this
1047 mov bl,[int_rm_map+ebx] ; IRQ -> interrupt
1048 ; store CS:IP and FLAGS on 16-bit stack, decrement stack pointer. DO NOT MODIFY EBX
1049 sub word [esp+0x20+4+16],6 ; (E)SP -= 6
1050 mov ax,word [esp+0x20+4+20] ; AX = SS (upper bits should be zero)
1051 shl eax,4 ; AX *= 16
1053 mov cx,word [esp+0x20+4+16] ; CX = SP
1054 add eax,ecx ; AX += SP AX = (SS*16)+SP
1055 mov cx,word [esp+0x20+4+4] ; IP
1056 mov word [es:eax],cx ; SS:SP+0 = IP
1057 mov cx,word [esp+0x20+4+8] ; CS
1058 mov word [es:eax+2],cx ; SS:SP+2 = CS
1059 mov cx,word [esp+0x20+4+12] ; FLAGS
1060 mov word [es:eax+4],cx ; SS:SP+4 = FLAGS
1061 ; replace CS:IP with values from real-mode interrupt table (EBX = interrupt vector)
1062 mov ax,[es:(ebx*4)] ; read from real-mode interrupt table (offset)
1063 mov word [esp+0x20+4+4],ax ; replace EIP
1064 mov ax,[es:(ebx*4)+2] ; .... (segment)
1065 mov word [esp+0x20+4+8],ax ; replace CS
1070 jmp .v86_complete_and_check_pending_irq_scan
1072 ; EXCEPTION HANDLING REACHES HERE IF IT TURNS OUT VM86 MODE WAS NOT INVOLVED
1074 call fault_collect_regs
1075 mov edx,0x0D ; INT 0x0D General Protection Fault
1076 mov esi,str_fault_0x0D
1077 jmp fault_jmp_unhandled
1079 .v86_iret: mov eax,FLAT32_SEL
1080 mov es,ax ; we'll need flat mode for this
1081 ; retrieve CS:IP and FLAGS from 16-bit stack, increment stack pointer
1082 mov ax,word [esp+0x20+20] ; AX = SS (upper bits should be zero)
1083 shl eax,4 ; AX *= 16
1085 mov cx,word [esp+0x20+16] ; CX = SP
1086 add eax,ecx ; AX += SP AX = (SS*16)+SP
1088 mov cx,word [es:eax] ; IP = SS:SP+0
1089 mov word [esp+0x20+4],cx ; IP
1091 mov cx,word [es:eax+2] ; CS = SS:SP+2
1092 mov word [esp+0x20+8],cx ; CS
1094 mov cx,word [es:eax+4] ; FLAGS = SS:SP+4
1095 mov word [esp+0x20+12],cx ; FLAGS
1097 add word [esp+0x20+16],6 ; (E)SP += 6
1102 jz .v86_int_api_detect
1104 jz .v86_int_api_unload
1106 .v86_int_api_detect:
1108 jmp .v86_complete_no_popad
1109 .v86_int_api_unload:
1110 mov ax,word [esp+4] ; save IP
1111 mov bx,word [esp+8] ; save CS
1112 mov [unload_int_ret+0],ax
1113 mov [unload_int_ret+2],bx
1115 mov ax,word [esp+16] ; save SP
1116 mov bx,word [esp+20] ; save SS
1117 mov [unload_int_stk+0],ax
1118 mov [unload_int_stk+2],bx
1121 ; V86 INT 3 (AL = 0xCC)
1122 .v86_int3: mov ah,0x03 ; convert to INT 3 (CD 03)
1123 inc dword [esp+0x20+4] ; step past (EIP++)
1124 jmp short .v86_int_n
1125 ; V86 INT x (AL = 0xCD AH = N)
1126 .v86_int: add dword [esp+0x20+4],2 ; EIP += 2
1127 ; V86 INT REFLECTION TO REAL MODE
1128 .v86_int_n: movzx ebx,ah ; EBX = interrupt number
1134 mov es,ax ; we'll need flat mode for this
1135 ; store CS:IP and FLAGS on 16-bit stack, decrement stack pointer. DO NOT MODIFY EBX
1136 sub word [esp+0x20+16],6 ; (E)SP -= 6
1137 mov ax,word [esp+0x20+20] ; AX = SS (upper bits should be zero)
1138 shl eax,4 ; AX *= 16
1140 mov cx,word [esp+0x20+16] ; CX = SP
1141 add eax,ecx ; AX += SP AX = (SS*16)+SP
1142 mov cx,word [esp+0x20+4] ; IP
1143 mov word [es:eax],cx ; SS:SP+0 = IP
1144 mov cx,word [esp+0x20+8] ; CS
1145 mov word [es:eax+2],cx ; SS:SP+2 = CS
1146 mov cx,word [esp+0x20+12] ; FLAGS
1147 mov word [es:eax+4],cx ; SS:SP+4 = FLAGS
1148 ; replace CS:IP with values from real-mode interrupt table (EBX = interrupt vector)
1149 mov ax,[es:(ebx*4)] ; read from real-mode interrupt table (offset)
1150 mov word [esp+0x20+4],ax ; replace EIP
1151 mov ax,[es:(ebx*4)+2] ; .... (segment)
1152 mov word [esp+0x20+8],ax ; replace CS
1155 .v86_cli: inc dword [esp+0x20+4] ; step past (EIP++)
1156 and word [esp+0x20+12],~0x200
1157 jmp .v86_complete_and_check_pending_irq
1159 .v86_sti: inc dword [esp+0x20+4] ; step past (EIP++)
1160 or word [esp+0x20+12],0x200
1161 jmp .v86_complete_and_check_pending_irq
1163 .v86_hlt: inc dword [esp+0x20+4] ; step past (EIP++)
1164 test word [esp+0x20+12],0x200
1165 jz .v86_hlt_with_cli
1166 jmp .v86_complete_and_check_pending_irq
1167 ; V86 HLT with interrupts disabled
1169 popad ; undo v86 check stack
1170 call fault_collect_regs
1172 mov esi,str_v86_hlt_cli
1173 jmp fault_jmp_unhandled
1175 .v86_pushf: mov eax,FLAT32_SEL
1177 inc dword [esp+0x20+4] ; step past (EIP++)
1178 sub word [esp+0x20+16],2 ; (E)SP -= 2
1179 mov ax,word [esp+0x20+20] ; AX = SS (upper bits should be zero)
1180 shl eax,4 ; AX *= 16
1182 mov cx,word [esp+0x20+16] ; CX = SP
1183 add eax,ecx ; AX += SP AX = (SS*16)+SP
1184 mov cx,word [esp+0x20+12] ; FLAGS
1185 mov word [es:eax],cx ; SS:SP+0 = FLAGS
1188 .v86_pushfd: mov eax,FLAT32_SEL
1190 add dword [esp+0x20+4],2 ; step past (EIP += 2)
1191 sub word [esp+0x20+16],4 ; (E)SP -= 4
1192 mov ax,word [esp+0x20+20] ; AX = SS (upper bits should be zero)
1193 shl eax,4 ; AX *= 16
1195 mov cx,word [esp+0x20+16] ; CX = SP
1196 add eax,ecx ; AX += SP AX = (SS*16)+SP
1197 mov ecx,dword [esp+0x20+12] ; EFLAGS
1198 mov dword [es:eax],ecx ; SS:SP+0 = FLAGS
1201 .v86_popf: mov eax,FLAT32_SEL
1203 inc dword [esp+0x20+4] ; step past (EIP++)
1204 mov ax,word [esp+0x20+20] ; AX = SS (upper bits should be zero)
1205 shl eax,4 ; AX *= 16
1207 mov cx,word [esp+0x20+16] ; CX = SP
1208 add eax,ecx ; AX += SP AX = (SS*16)+SP
1209 mov cx,word [es:eax] ; FLAGS = SS:SP+0
1210 mov word [esp+0x20+12],cx ; FLAGS
1211 add word [esp+0x20+16],2 ; (E)SP += 2
1212 jmp .v86_complete_and_check_pending_irq
1214 .v86_popfd: mov eax,FLAT32_SEL
1216 add dword [esp+0x20+4],2 ; step past (EIP += 2)
1217 mov ax,word [esp+0x20+20] ; AX = SS (upper bits should be zero)
1218 shl eax,4 ; AX *= 16
1220 mov cx,word [esp+0x20+16] ; CX = SP
1221 add eax,ecx ; AX += SP AX = (SS*16)+SP
1222 mov ecx,dword [es:eax] ; EFLAGS = SS:SP+0
1223 or ecx,0x20000 ; make sure the VM bit is set
1224 mov dword [esp+0x20+12],ecx ; EFLAGS
1225 add word [esp+0x20+16],4 ; (E)SP += 4
1226 jmp .v86_complete_and_check_pending_irq
1227 ; UNKNOWN OPCODE AT CS:IP in V8086 MODE
1228 .v86_unknown: popad ; undo v86 check stack
1229 add esp,4 ; toss real error code
1230 push dword [v86_raw_opcode] ; the "ERROR CODE" are the 4 bytes at CS:IP
1231 call fault_collect_regs
1233 mov esi,str_v86_unknown
1234 jmp fault_jmp_unhandled
1235 ; API CALL TO SHUTDOWN VM86 MONITOR
1236 v86_api_exit: mov ax,FLAT32_SEL
1240 ; FIXME: I give up... why does JMPing to TSS_SEL:0 cause random crashes in VirtualBox?
1241 jmp _exit_from_prot32 ; and then begin shutdown of this program
1243 ; ========== FAULT COLLECTION ROUTINE. SS:ESP should point to fault. If the exception does not push an error code,
1244 ; then the caller must push a dummy error code
1253 mov word [unhandled_fault_var_ds],bx
1256 mov eax,[esp+4+8+0] ; ERROR CODE ON STACK +2 DWORDs PUSHED
1257 mov dword [unhandled_fault_var_errcode],eax
1259 mov eax,[esp+4+8+4] ; EIP ON STACK
1260 mov dword [unhandled_fault_var_eip],eax
1262 mov eax,[esp+4+8+8] ; CS ON STACK
1263 mov word [unhandled_fault_var_cs],ax
1265 mov eax,[esp+4+8+12] ; EFLAGS ON STACK
1266 mov dword [unhandled_fault_var_eflags],eax
1268 call .retr_stack_ptr
1272 mov dword [unhandled_fault_var_eax],eax
1273 mov dword [unhandled_fault_var_ebx],ebx
1274 mov dword [unhandled_fault_var_ecx],ecx
1275 mov dword [unhandled_fault_var_edx],edx
1276 mov dword [unhandled_fault_var_esi],esi
1277 mov dword [unhandled_fault_var_edi],edi
1278 mov dword [unhandled_fault_var_ebp],ebp
1281 mov dword [unhandled_fault_var_cr0],eax
1283 mov dword [unhandled_fault_var_cr3],eax
1285 mov dword [unhandled_fault_var_cr4],eax
1287 mov word [unhandled_fault_var_es],ax
1289 mov word [unhandled_fault_var_fs],ax
1291 mov word [unhandled_fault_var_gs],ax
1294 ; if privilege escalation was involved (stack switching) then retrieve SS:ESP at fault from the stack frame.
1295 ; else retrieve from actual SS:ESP registers
1297 test word [unhandled_fault_var_cs],3 ; if code segment is nonzero
1298 jz .retr_stack_ptr_ring_0
1300 mov eax,[esp+4+4+8+16] ; ESP ON STACK
1301 mov dword [unhandled_fault_var_esp],eax
1303 mov eax,[esp+4+4+8+20] ; SS ON STACK
1304 mov word [unhandled_fault_var_ss],ax
1307 .retr_stack_ptr_ring_0:
1308 lea eax,[esp+4+4+8+16] ; +4 our call frame, +8 PUSH DS,EAX +16 GPF stack frame
1309 mov dword [unhandled_fault_var_esp],eax
1311 mov eax,ss ; SS ON STACK
1312 mov word [unhandled_fault_var_ss],ax
1316 fault_jmp_unhandled:
1317 jmp CODE16_SEL:.thunk16
1319 .thunk16: mov ax,DATA16_SEL
1324 jmp unhandled_fault_errcode
1327 ; ============= cleanup, exit to DOS (from 32-bit protected mode)
1329 jmp CODE16_SEL:.entry16
1331 .entry16: mov ax,DATA16_SEL
1339 ; ============= cleanup, exit to DOS (from 16-bit protected mode)
1348 ; overwrite the far jmp's segment value
1349 mov ax,[my_realmode_seg]
1350 mov word [.real_hackme+3],ax
1355 .real_hackme: jmp 0:.real_entry
1356 .real_entry: mov ax,[my_realmode_seg]
1364 ; reprogram the PIC back to what normal DOS expects: IRQ 0-7 => INT 8-15
1365 mov al,0x10 ; ICW1 A0=0
1367 mov al,0x08 ; ICW2 A0=1
1369 mov al,0x04 ; ICW3 A0=1 slave on IRQ 2
1372 ; remove our INT 66h API
1375 mov word [es:(RM_INT_API*4)],ax
1376 mov word [es:(RM_INT_API*4)+2],ax
1378 ; free HIMEM.SYS blocks
1379 mov ah,0Dh ; HIMEM.SYS function 0Dh unlock memory block
1380 mov dx,word [himem_sys_buffer_handle]
1381 call far word [himem_sys_entry]
1382 mov ah,0Ah ; HIMEM.SYS function 0Ah free memory block
1383 mov dx,word [himem_sys_buffer_handle]
1384 call far word [himem_sys_entry]
1386 ; if we already exited as a TSR...
1387 test byte [i_am_tsr],1
1390 ; time to exit to DOS
1391 mov dx,str_exit_to_dos
1394 ; ============= ALTERNATE EXIT IF WE ALREADY EXITED AS TSR
1397 mov es,ax ; ES = our code segment which is also our PSP segment
1398 mov ah,0x49 ; function 49h free memory block
1401 jnc .tsr_exit_free_ok
1402 mov dx,str_cannot_free_self
1406 mov ax,[cs:unload_int_stk+0] ; offset
1407 add ax,6 ; discard prior frame
1409 mov ax,[cs:unload_int_stk+2] ; segment
1412 mov dx,str_exit_to_dos
1415 jmp far word [cs:unload_int_ret]
1417 ; ============= UNHANDLED FAULT HANDLER (16-bit code)
1418 ; input: EDX = Number of interrupt
1419 ; DS:SI = Textual string of fault
1420 ; ESP = Stack containing:
1422 unhandled_fault_errcode:
1427 mov ax,[cs:my_realmode_seg]
1428 mov word [.real16jmp+3],ax
1438 ; crash-thunk to real mode
1441 .real16jmp: jmp 0:.real16
1442 .real16: mov ax,[cs:my_realmode_seg]
1457 ; print exception name on screen
1459 call .unhandled_print
1460 mov al,' ' ; +space plus AH still contains upper byte from .unhandled_print
1463 ; then the number (in EDX) write to DS:DI
1467 call eax_to_hex_16_dos
1468 lea si,[di+6] ; only the last two hex digits
1470 call .unhandled_print
1472 ; print the registers.
1473 ; during this loop: SI = print list EDI = location on screen to draw [ESP] = location on screen of row start
1474 mov edi,0xB8000+(160*2) ; two lines down
1479 jz .regprint32e ; AX=0 STOP
1481 jz .regprint32nl ; AX=1 GO TO NEW LINE
1483 mov si,ax ; SI=AX=address of variable name
1485 call .unhandled_print
1487 mov ax,0x4E00 | (':')
1489 lodsw ; SI=address of variable
1495 call eax_to_hex_16_dos
1498 call .unhandled_print
1500 mov ax,0x4E00 | (' ')
1503 .regprint32nl: pop edi
1504 add edi,160 ; move to next line, save back to [ESP]
1507 .regprint32e: pop edi
1509 add edi,160 ; next line...
1514 jz .regprint16e ; AX=0 STOP
1516 jz .regprint16nl ; AX=1 GO TO NEW LINE
1518 mov si,ax ; SI=AX=address of variable name
1520 call .unhandled_print
1522 mov ax,0x4E00 | (':')
1524 lodsw ; SI=address of variable
1531 call eax_to_hex_16_dos
1534 call .unhandled_print
1536 mov ax,0x4E00 | (' ')
1539 .regprint16nl: pop edi
1540 add edi,160 ; move to next line, save back to [ESP]
1543 .regprint16e: mov si,str_mode_prot ; CPU mode
1544 test dword [unhandled_fault_var_eflags],0x20000
1545 jz .regprint_cpu_mode_not_v86
1547 .regprint_cpu_mode_not_v86:
1548 call .unhandled_print
1573 ; ===== print on screen from DS:SI to ES:EDI
1577 jz .unhandled_printe
1580 jmp .unhandled_print
1584 ; ============= Entry point (virtual 8086 mode)
1585 vm86_entry: cli ; make sure the v86 monitor handles CLI
1587 pushf ; ...and PUSHF
1589 pushfd ; ...32-bit PUSHF
1590 popfd ; ...32-bit POPF
1592 out 21h,al ; ...OUT?
1594 ; NOW MAKE SURE PUSHF/POPF STORE THE VALUE ON-STACK LIKE THEY'RE SUPPOSED TO
1596 mov word [ss:bx-2],0x5A5A
1599 cmp word [ss:bx],0x5A5A
1600 jnz .pushf_ok ; if the value DIDN'T CHANGE then the monitor failed to write FLAGS to stack
1620 mov dword [ss:bx-4],0x5A5A5A5A
1623 cmp dword [ss:bx],0x5A5A5A5A
1624 jnz .pushfd_ok ; if the value DIDN'T CHANGE then the monitor failed to write FLAGS to stack
1642 ; IF I CLEAR INTERRUPT (CLI) AND THEN EXECUTE AN INTERRUPT, DOES IT COME BACK ENABLED?
1644 mov ah,0x0F ; INT 10 AH=0x0F which has no visisible effect
1649 jz .int_doesnt_enable
1655 mov si,str_vm86_hello
1658 ; TEST DEFERRED IRQ MECHANISM BY DELIBERATLEY HALTING FOR AWHILE
1660 mov ecx,0x1000000 ; delibrate slow countdown loop
1665 ; for my next trick, I will exit to DOS as a TSR
1666 ; and allow the user to run the whole DOS kernel this way :)
1667 mov es,[cs:0x2C] ; locate our environment block and free it
1668 mov ah,0x49 ; function 49h free memory block
1673 .env_free_ok: mov word [cs:0x2C],0 ; rub out the ENV block
1675 ; setup our INT 66h API
1678 mov word [es:(RM_INT_API*4)],realmode_api_entry
1680 mov word [es:(RM_INT_API*4)+2],ax
1682 ; finally, terminate and stay resident
1683 mov byte [i_am_tsr],1
1684 mov edx,the_end ; DX = memory in paragraphs to save
1687 add edx,16 ; <-- FIXME: IS THIS NECESSARY
1688 mov ah,0x31 ; function 31h terminate and stay resident
1691 ; ============= "Secret Handshake" to exit back into the v86 monitor and shutdown the program (virtual 8086 mode)
1692 ; TODO: Remove this, call into RM_INT_API instead
1693 vm86_exit: mov eax,0xAABBAABB
1697 ; ============= If any of our self-test fails, we draw DIRECTLY ON VGA RAM and hike back into the vm86 monitor ASAP.
1698 ; if self-tests fail chances are calling the BIOS/DOS will cause major problems. AX=CODE
1699 vm86_errcode: mov bx,0xB800
1702 or ax,0x4E30 ; AX = VGA alphanumeric code for that number
1706 ; ============= Real-mode API entry (reflect to v86 monitor by executing an INT)
1707 ; this would allow the trick to work even for programs that direct-call instead
1712 ; ============= Parse command line (from PSP segment)
1726 ; FALL THROUGH WITH ZF=0 to return
1728 ; AT THIS POINT: SI = just after the / or - in the switch
1736 ; the A-Z switches are allowed to have "=NNNN" after them where N is some integer in hex or decimal
1739 xor bh,bh ; BX = index into lookup table
1741 jmp word [bx+.switch_az]
1743 .help: or al,al ; AL != 0 => ZF=0
1745 .unknown_switch:mov dx,str_unknown_switch
1747 lea dx,[si-2] ; step back two chars
1753 ; ========== Switches CALL here if they need a numeric value to follow.
1754 ; returns to caller if so, parsing as 16-bit integer returned in EAX. Else,
1755 ; it discards the return address and jumps to the 'needs param' error message.
1756 .switch_needs_equ_check:
1758 jnz .switch_needs_equ_check_fail
1764 .switch_needs_equ_check_fail:
1765 add sp,2 ; fall through
1767 mov dx,str_needs_equals
1770 ; ========== /B=<number>
1771 .switch_buffer_size:
1772 call .switch_needs_equ_check
1774 mov [himem_sys_buffer_size],eax
1777 .switch_unload: mov byte [user_req_unload],1
1780 .switch_iopl: mov byte [user_req_iopl],0
1782 ; switch A-Z jump table
1783 .switch_az: dw .unknown_switch ; /A
1784 dw .switch_buffer_size ; /B=<number>
1785 dw .unknown_switch ; /C
1786 dw .unknown_switch ; /D
1787 dw .unknown_switch ; /E
1788 dw .unknown_switch ; /F
1789 dw .unknown_switch ; /G
1790 dw .unknown_switch ; /H
1791 dw .switch_iopl ; /I
1792 dw .unknown_switch ; /J
1793 dw .unknown_switch ; /K
1794 dw .unknown_switch ; /L
1795 dw .unknown_switch ; /M
1796 dw .unknown_switch ; /N
1797 dw .unknown_switch ; /O
1798 dw .unknown_switch ; /P
1799 dw .unknown_switch ; /Q
1800 dw .unknown_switch ; /R
1801 dw .unknown_switch ; /S
1802 dw .unknown_switch ; /T
1803 dw .switch_unload ; /U
1804 dw .unknown_switch ; /V
1805 dw .unknown_switch ; /W
1806 dw .unknown_switch ; /X
1807 dw .unknown_switch ; /Y
1808 dw .unknown_switch ; /Z
1810 irq_routines: dw irq_0
1827 fault_routines: dw fault_0x00
1861 ; register print list
1862 printlist_32: dw str_eax, unhandled_fault_var_eax
1863 dw str_ebx, unhandled_fault_var_ebx
1864 dw str_ecx, unhandled_fault_var_ecx
1865 dw str_edx, unhandled_fault_var_edx
1866 dw str_esi, unhandled_fault_var_esi
1867 dw str_edi, unhandled_fault_var_edi
1869 dw str_ebp, unhandled_fault_var_ebp
1870 dw str_esp, unhandled_fault_var_esp
1871 dw str_eip, unhandled_fault_var_eip
1872 dw str_eflags, unhandled_fault_var_eflags
1873 dw str_errcode, unhandled_fault_var_errcode
1874 dw str_cr0, unhandled_fault_var_cr0
1876 dw str_cr3, unhandled_fault_var_cr3
1877 dw str_cr4, unhandled_fault_var_cr4
1879 printlist_16: dw str_cs, unhandled_fault_var_cs
1880 dw str_ds, unhandled_fault_var_ds
1881 dw str_es, unhandled_fault_var_es
1882 dw str_fs, unhandled_fault_var_fs
1883 dw str_gs, unhandled_fault_var_gs
1884 dw str_ss, unhandled_fault_var_ss
1887 ; ============= bios_puts (print $-terminated string at DS:SI)
1903 ; ============= dos_puts (print $-terminated string at DS:DX)
1904 dos_puts: mov ah,09h
1908 ; ============= read one digit from DS:SI return in AX (16-bit code)
1909 ax_strtol_16_single:mov al,[si]
1922 ; ============= read from DS:SI and convert numerical string to integer value return in AX (16-bit code)
1923 ax_strtol_16: xor cx,cx
1925 call ax_strtol_16_single
1930 shl cx,3 ; BX = CX * 2, CX *= 8
1931 add cx,bx ; CX = (CX * 8) + (CX * 2) = CX * 10
1932 add cx,ax ; CX += new digit
1937 ; ============= take AX and write to buffer (DS:SI) as hexadecimal string (16-bit code)
1938 al_to_hex_16_dos:mov byte [di+2],'$'
1940 al_to_hex_16_nul:mov byte [di+2],0
1941 al_to_hex_16: push di
1948 mov al,[bx+str_hex] ; AL' = str_hex[al]
1951 mov ah,[bx+str_hex] ; AH' = str_hex[ah]
1959 ; ============= take AX and write to buffer (DS:SI) as hexadecimal string (16-bit code)
1960 ax_to_hex_16_dos:mov byte [di+4],'$'
1962 ax_to_hex_16_nul:mov byte [di+4],0
1963 ax_to_hex_16: push di
1973 ; ============= take EAX and write to buffer (DS:DI) as hexadecimal string (16-bit code)
1974 eax_to_hex_16_dos:mov byte [di+8],'$'
1976 eax_to_hex_16_nul:mov byte [di+8],0
1977 eax_to_hex_16: push di
1987 ; ============= /U Unloading the resident copy of this program
1988 unload_this_program:
1992 mov dx,str_not_loaded
1997 mov bx,[es:(RM_INT_API*4)]
1998 or cx,[es:(RM_INT_API*4)+2]
1999 cmp cx,0 ; if pointer is 0000:0000
2005 .v86_is_me: mov ax,cs
2010 mov dx,str_removing_self
2012 ; instruct it to remove itself
2015 ; exit, having done our job
2020 .v86_not_me: mov dx,str_v86_but_not_me
2023 ; ============= DATA: THESE EXIST IN THE .COM BINARY IMAGE
2024 section .data align=2
2025 himem_sys_buffer_size:dd (256*1024) ; DWORD [amount of extended memory to allocate]
2026 str_require_386:db '386 or higher required$'
2027 str_removing_self:db 'Removing from memory',13,10,'$'
2028 str_v86_detected:db 'Virtual 8086 mode already active$'
2029 str_v86_but_not_me:db 'Virtual 8086 active, and its not me$'
2030 str_not_loaded: db 'Not resident in memory$'
2031 str_cannot_free_self:db 'Cannot free self from memory$'
2032 str_need_himem_sys:db 'HIMEM.SYS not installed$'
2033 str_himem_a20_error:db 'HIMEM.SYS failed to enable A20$'
2034 str_himem_alloc_err:db 'Unable to alloc extended memory$'
2035 str_himem_lock_err:db 'Unable to lock extended memory$'
2036 str_buffer_too_small:db 'Buffer too small$'
2037 str_buffer_too_large:db 'Buffer too large$'
2038 str_unloaded: db 'Unloaded',13,10,'$'
2039 str_buffer_at: db 'Buffer at: $'
2040 str_crlf: db 13,10,'$'
2041 str_hex: db '0123456789ABCDEF'
2042 str_help: db 'V86KERN [options]',13,10
2043 db 'Demonstration Virtual 8086 kernel/monitor',13,10
2045 db 'Options start with - or /',13,10
2046 db ' /? Show this help',13,10
2047 db ' /B=... Set buffer size (in KB)',13,10
2048 db ' /U Unload the kernel',13,10
2049 db ' /I Run with IOPL=3 (trap CLI/STI/etc)',13,10
2051 str_unknown_switch:db 'Unknown switch $'
2052 str_needs_equals:db 'Switch missing =...$'
2062 str_errcode: db 'ERR$'
2063 str_eflags: db 'FLG$'
2073 str_mode_prot: db 'Protected mode$'
2074 str_mode_v86: db 'Virtual 8086 mode$'
2075 str_vm86_hello: db 'This text was printed by the Virtual 8086 mode component of this program',13,10,'$'
2076 str_fault_0x00: db 'Divide by Zero$'
2077 str_fault_0x01: db 'Debug$'
2078 str_fault_0x02: db 'NMI$'
2079 str_fault_0x03: db 'Breakpoint$'
2080 str_fault_0x04: db 'Overflow$'
2081 str_fault_0x05: db 'Boundary Check$'
2082 str_fault_0x06: db 'Invalid Opcode$'
2083 str_fault_0x07: db 'Coprocessor N/A$'
2084 str_fault_0x08: db 'Double Fault$'
2085 str_fault_0x09: db 'Coprocessor Segment Overrun$'
2086 str_fault_0x0A: db 'Invalid TSS$'
2087 str_fault_0x0B: db 'Segment Not Present$'
2088 str_fault_0x0C: db 'Stack Fault$'
2089 str_fault_0x0D: db 'General Protection Fault$'
2090 str_fault_0x0E: db 'Page Fault$'
2091 str_fault_0x0F: db 'Exception F$'
2092 str_fault_0x10: db 'FPU Error$'
2093 str_fault_0x11: db 'Alignment Check$'
2094 str_fault_0x12: db 'Machine Check$'
2095 str_fault_0x13: db 'SIMD/SSE Exception$'
2096 str_fault_unknown:db 'Unknown exception$'
2097 str_v86_unknown:db 'Unknown instruction in v86 mode$'
2098 str_v86_hlt_cli:db 'v86 halt with interrupts disabled$'
2099 str_v86_secret: db 'Inappropriate use of v86 secret handshake$'
2100 str_exit_to_dos:db 'Shutdown successful, exiting to DOS$'
2101 str_irq_deferred:db 'Deferred IRQ$'
2102 str_irq_1: db 'IRQ #1$'
2104 ; ============= VARIABLES: THESE DO NOT EXIST IN THE .COM FILE THEY EXIST IN MEMORY FOLLOWING THE BINARY IMAGE
2105 section .bss align=2
2106 ; ---------------------- STACK
2107 stack_base: resb 4096 ; char[4096+4]
2108 stack_init: resd 1 ; DWORD
2110 scratch_str: resb 64 ; char[64]
2111 ; ---------------------- STACK
2112 stack_base_vm86:resb 4096 ; char[4096+4]
2113 stack_init_vm86:resd 2 ; DWORD
2115 ; ---------------------- HIMEM.SYS state
2116 himem_sys_entry:resd 1 ; FAR POINTER
2117 himem_sys_buffer_phys:resd 1 ; DWORD [physical memory address]
2118 himem_sys_buffer_handle:resw 1 ; WORD [HIMEM.SYS handle]
2119 ; ---------------------- my real mode segment
2120 my_realmode_seg:resw 1 ; WORD
2121 my_phys_base: resd 1 ; DWORD
2122 tss_phys_base: resd 1 ; DWORD base logical address of TSS
2123 tss_vm86_phys_base:resd 1 ; DWORD base logical address of TSS
2124 buffer_alloc: resd 1 ; DWORD
2125 kern32_stack_base:resd 1 ; DWORD
2126 kern32_stack_top:resd 1 ; DWORD
2127 v86_raw_opcode: resd 1 ; DWORD
2129 ; ---------------------- GDTR/IDTR
2130 gdtr_pmode: resq 1 ; LIMIT. BASE
2131 gdtr_real: resq 1 ; LIMIT, BASE
2132 idtr_pmode: resq 1 ; LIMIT, BASE
2133 idtr_real: resq 1 ; LIMIT, BASE
2134 ; ---------------------- GLOBAL DESCRIPTOR TABLE
2136 gdt: resq (MAX_SEL/8) ; 16 GDT entries
2137 ; ---------------------- INTERRUPT DESCRIPTOR TABLE
2139 idt: resq 256 ; all 256
2140 ; ---------------------- STATE
2143 user_req_unload:resb 1
2144 user_req_iopl: resb 1
2146 unload_int_ret: resd 1
2147 unload_int_stk: resd 1
2148 ; ---------------------- WHEN DISPLAYING THE UNHANDLED FAULT DIALOG
2149 unhandled_fault_var_errcode:resd 1
2150 unhandled_fault_var_eax:resd 1
2151 unhandled_fault_var_ebx:resd 1
2152 unhandled_fault_var_ecx:resd 1
2153 unhandled_fault_var_edx:resd 1
2154 unhandled_fault_var_esi:resd 1
2155 unhandled_fault_var_edi:resd 1
2156 unhandled_fault_var_ebp:resd 1
2157 unhandled_fault_var_esp:resd 1
2158 unhandled_fault_var_eip:resd 1
2159 unhandled_fault_var_eflags:resd 1
2160 unhandled_fault_var_cr0:resd 1
2161 unhandled_fault_var_cr3:resd 1
2162 unhandled_fault_var_cr4:resd 1
2163 unhandled_fault_var_cs:resw 1
2164 unhandled_fault_var_ds:resw 1
2165 unhandled_fault_var_es:resw 1
2166 unhandled_fault_var_fs:resw 1
2167 unhandled_fault_var_gs:resw 1
2168 unhandled_fault_var_ss:resw 1
2169 ; ---------------------------------------------------------------------
2171 ; ---------------------------------------------------------------------
2172 padding: resq 2 ; SAFETY PADDING