;//--------------------------------------------------------------- ;// emulate.s (a modification of our 'tryvm86.s' demo) ;// ;// This program, after entering protected-mode, executes ;// a real-mode procedure in Virtual-8086 emulation mode. ;// This program emulates the 'io-sensitive' instructions ;// which occur in VM86-mode if IOPL<3, and which trigger ;// General Protection Faults with an error-code of zero. ;// ;// assemble with: $ as86 tryvm86.s -b tryvm86.b ;// install using: $ dd if=tryvm86.b of=/dev/fd0 seek=1 ;// ;// NOTE: This code begins executing with CS:IP = 1000:0002. ;// ;// programmer: ALLAN CRUSE ;// written on: 23 MAR 2004 ;// revised on: 28 APR 2004 -- added io-sensitive emulations ;// bug repair: 28 APR 2004 -- enlarged TSS to include iomap ;//--------------------------------------------------------------- .WORD 0xABCD ; programming signature ;----------------------------------------------------------------- main: mov ax, cs ; address this segment mov ds, ax ; with DS register pop dword return_address ; store return-address mov ss, ax ; adjust SS register lea esp, tos0 ; and set new stacktop call initialize_os_tables call enter_protected_mode call execute_program_demo call leave_protected_mode push dword return_address ; recover our exit-address retf ; exit to program launcher ;----------------------------------------------------------------- return_address: .LONG 0, tos0, sel_ss ; to store exit-address ;----------------------------------------------------------------- ; EQUATES realCS EQU 0x1000 ; segment-address of code limGDT EQU 0x002F ; allocates 6 descriptors sel_es EQU 0x0008 ; vram-segment selector sel_cs EQU 0x0010 ; code-segment selector sel_ss EQU 0x0018 ; data-segment selector sel_ts EQU 0x0020 ; TSS-segment selector sel_fs EQU 0x0028 ; flat-segment selector ;----------------------------------------------------------------- .ALIGN 8 theGDT: .WORD 0x0000, 0x0000, 0x0000, 0x0000 ; null descriptor .WORD 0x7FFF, 0x8000, 0x920B, 0x0000 ; vram descriptor .WORD 0xFFFF, 0x0000, 0x9A01, 0x0000 ; code descriptor .WORD 0xFFFF, 0x0000, 0x9201, 0x0000 ; data descriptor .WORD 0x0168, theTSS, 0x8901, 0x0000 ; TSS descriptor .WORD 0xFFFF, 0x0000, 0x9200, 0x008F ; flat descriptor ;----------------------------------------------------------------- theIDT: .SPACE 2048 ; enough for 256 gate-descriptors ;----------------------------------------------------------------- ;----------------------------------------------------------------- enter_protected_mode: cli ; no device interrupts mov eax, cr0 ; get machine status bts eax, #0 ; set PE-bit to 1 mov cr0, eax ; enable protection push #0x0001 ; push base-address hiword push #theGDT ; push base-address loword push #limGDT ; push GDT's segment-limit lgdt [esp] ; load GDTR register-image add sp, #6 ; discard three stackwords push #0x0001 ; push base-address hiword push #theIDT ; push base-address loword push #0x07FF ; push IDT's segment-limit lidt [esp] ; load IDTR register-image add sp, #6 ; discard three stackwords jmpf #pm, #sel_cs ; reload register CS pm: mov ax, #sel_ss mov ss, ax ; reload register SS mov ds, ax ; reload register DS xor ax, ax ; use 'null' selector mov es, ax ; to purge invalid ES mov fs, ax ; to purge invalid FS mov gs, ax ; to purge invalid GS ret ; back to main routine ;----------------------------------------------------------------- leave_protected_mode: mov ax, ss ; address 64KB r/w segment mov ds, ax ; using DS register mov es, ax ; and ES register mov eax, cr0 ; get machine status btr eax, #0 ; reset PE-bit to 0 mov cr0, eax ; disable protection push #0x0000 ; push base-address hiword push #0x0000 ; push base-address loword push #0x03FF ; push IDT's segment-limit lidt [esp] ; load IDTR register-image add sp, #6 ; discard three stackwords jmpf #rm, #realCS ; reload register CS rm: mov ax, cs mov ss, ax ; reload register SS mov ds, ax ; reload register DS sti ; interrupts allowed ret ; back to main routine ;----------------------------------------------------------------- ;----------------------------------------------------------------- theTSS: .SPACE 0x168 ; 32bit Task-State Segment ;----------------------------------------------------------------- initialize_os_tables: ; initialize IDT descriptor for gate 0x0D mov edi, #0x0D ; ID-number for GP-fault lea di, theIDT[edi*8] ; address gate-descriptor mov 0[di], #isrGPF ; entry-point loword mov 2[di], #sel_cs ; selector for code mov 4[di], #0x8E00 ; 386 interrupt-gate mov 6[di], #0x0000 ; entry-point hiword ; initialize 'iomap' field in TSS lea di, theTSS ; address our TSS segment mov 0x66[di], #0x68 ; where the bitmap begins ret ;----------------------------------------------------------------- execute_program_demo: mov theTSS+4, esp ; current stack-address mov theTSS+8, ss ; preserved in 'theTSS' mov ax, #sel_ts ; establish 'theTSS' as ltr ax ; Task-State Segment pushfd ; insure NT-bit is clear btr [esp], #14 ; in the EFLAGS register popfd ; before executing iretd push dword #0 ; register-image for GS push dword #0 ; register-image for FS push dword #0 ; register-image for DS push dword #0 ; register-image for ES push dword #realCS ; register-image for SS push dword #tos3 ; register-image for SP push dword #0x00020000 ; EFLAGS (note: IOPL=0) push dword #realCS ; register-image for CS push dword #write_tty ; register-image for IP iretd ; enter Virtual-8086 mode ;----------------------------------------------------------------- finish_up_main_thread: CSEG ; address theTSS with CS lss esp, theTSS+4 ; restore saved stackptr ret ; return to main routine ;----------------------------------------------------------------- msg: .ASCII " Hello from Virtual-8086 mode \n\r" len: .WORD * - msg ; length of message-text att: .BYTE 0x1F ; intense white upon blue ;----------------------------------------------------------------- write_tty: mov ax, cs mov ds, ax mov es, ax mov ah, #0x0F int 0x10 mov ah, #0x03 int 0x10 lea bp, msg mov cx, len mov bl, att mov ax, #0x1301 int 0x10 hlt ; privileged instruction ;----------------------------------------------------------------- ;----------------------------------------------------------------- isrGPF: ; verify that the exception occurred in Virtual-8086 mode bt dword [esp+12], #17 ; was VM-flag set? jc emulate ; yes, do emulation jmpf #finish_up_main_thread, #sel_cs ; else quit emulate: push ebp mov ebp, esp pushad mov ax, #sel_fs ; address 4GB memory mov ds, ax ; with DS register ; compute address of the faulting instruction in ESI mov si, [ebp+12] ; fetch the CS-image movzx esi, si ; extend to 32 bits shl esi, #4 ; sixteen times CS mov ax, [ebp+8] ; fetch the IP-image movzx eax, ax ; extend to 32 bits add esi, eax ; add offset to base ; switch on the faulting instruction's opcode cmp byte [esi], #0xCD ; was it 'int-nn'? beq emulate_int ; yes, then emulate cmp byte [esi], #0xCF ; was it 'iret'? beq emulate_iret ; yes, then emulate cmp byte [esi], #0x9C ; was it 'pushf'? beq emulate_pushf ; yes, then emulate cmp byte [esi], #0x9D ; was it 'popf'? beq emulate_popf ; yes, then emulate cmp byte [esi], #0xFA ; was it 'cli'? beq emulate_cli ; yes, then emulate cmp byte [esi], #0xFB ; was it 'sti'? beq emulate_sti ; yes, then emulate ; more emulations can go here (e.g., pushfd/popfd/iretd) jmpf #finish_up_main_thread, #sel_cs ; else quit em_exit: popad pop ebp add esp, #4 iretd ;----------------------------------------------------------------- emulate_int: ; advance IP-image past the two-byte 'int-nn' instruction add word [ebp+8], #2 ; advance the IP-image ; decrement SP-image to make room for three pushed words sub word [ebp+20], #6 ; make room for 3 words ; compute address of the ring3 stacktop in EDI mov di, [ebp+24] ; fetch the SS-image movzx edi, di ; extend to 32 bits shl edi, #4 ; sixteen times CS mov ax, [ebp+20] ; fetch the SP-image movzx eax, ax ; extend to 32 bits add edi, eax ; add offset to base ; transfer IP, CS, and FLAGS images to the ring3 stack mov ax, [ebp+8] ; fetch IP-image mov [edi+0], ax ; store IP-image mov ax, [ebp+12] ; fetch CS-image mov [edi+2], ax ; store CS-image mov ax, [ebp+16] ; fetch FL-image mov [edi+4], ax ; store FL-image ; clear the IF-bit and TF-bit in the EFLAGS image btr dword [ebp+16], #9 ; reset CF-bit btr dword [ebp+16], #8 ; reset TF-bit ; get the interrupt ID-number in EBX mov ax, [esi] ; fetch int-nn instruction movzx ebx, ah ; extend int-ID to 32-bits ; use real-mode interrupt-vector as the return-address mov ax, [ebx*4 + 0] ; get vector loword mov [ebp+8], ax ; setup as IP-image mov ax, [ebx*4 + 2] ; get vector hiword mov [ebp+12], ax ; setup as CS-image ; ok, we're ready to return to the VM86 task jmp em_exit ;----------------------------------------------------------------- emulate_iret: ; advance IP-image past the one-byte 'iret' instruction add word [ebp+8], #1 ; advance the IP-image ; compute address of the ring3 stacktop in EDI mov di, [ebp+24] ; fetch the SS-image movzx edi, di ; extend to 32 bits shl edi, #4 ; sixteen times CS mov ax, [ebp+20] ; fetch the SP-image movzx eax, ax ; extend to 32 bits add edi, eax ; add offset to base ; transfer IP, CS, and FLAGS images to the ring0 stack mov ax, [edi+0] ; fetch IP-image mov [ebp+8], ax ; store IP-image mov ax, [edi+2] ; fetch CS-image mov [ebp+12], ax ; store CS-image mov ax, [edi+4] ; fetch FL-image mov [ebp+16], ax ; store FL-image ; increment SP-image to discard the three poped words add word [ebp+20], #6 ; discard 3 words ; ok, we're ready to return to the VM86 task br em_exit ;----------------------------------------------------------------- emulate_pushf: ; advance IP-image past the one-byte 'pushf' instruction add word [ebp+8], #1 ; advance the IP-image ; decrement SP-image to make room for the pushed word sub word [ebp+20], #2 ; make room for 1 word ; compute address of the ring3 stacktop in EDI mov di, [ebp+24] ; fetch the SS-image movzx edi, di ; extend to 32 bits shl edi, #4 ; sixteen times CS mov ax, [ebp+20] ; fetch the SP-image movzx eax, ax ; extend to 32 bits add edi, eax ; add offset to base ; transfer FLAGS images to the ring3 stack mov ax, [ebp+16] ; fetch FL-image mov [edi+0], ax ; store FL-image ; ok, we're ready to return to the VM86 task br em_exit ;----------------------------------------------------------------- emulate_popf: ; advance IP-image past the one-byte 'popf' instruction add word [ebp+8], #1 ; advance the IP-image ; compute address of the ring3 stacktop in EDI mov di, [ebp+24] ; fetch the SS-image movzx edi, di ; extend to 32 bits shl edi, #4 ; sixteen times CS mov ax, [ebp+20] ; fetch the SP-image movzx eax, ax ; extend to 32 bits add edi, eax ; add offset to base ; transfer FLAGS images to the ring0 stack mov ax, [edi+0] ; fetch FL-image mov [ebp+16], ax ; store FL-image ; increment SP-image to discard the poped word add word [ebp+20], #2 ; discard 1 word ; ok, we're ready to return to the VM86 task br em_exit ;----------------------------------------------------------------- emulate_cli: ; advance IP-image past the one-byte 'cli' instruction add word [ebp+8], #1 ; advance the IP-image ; clear the IF-bit in the EFLAGS register-image btr dword [ebp+16], #9 ; reset the IF-bit ; ok, we're ready to return to the VM86 task br em_exit ;----------------------------------------------------------------- emulate_sti: ; advance IP-image past the one-byte 'sti' instruction add word [ebp+8], #1 ; advance the IP-image ; set the IF-bit in the EFLAGS register-image bts dword [ebp+16], #9 ; set the IF-bit ; ok, we're ready to return to the VM86 task br em_exit ;----------------------------------------------------------------- .ALIGN 16 ; alignment at paragraph .SPACE 512 ; reserved for stack use tos3: ; label fop top-of-stack ;----------------------------------------------------------------- .ALIGN 16 ; alignment at paragraph .SPACE 512 ; reserved for stack use tos0: ; label fop top-of-stack ;----------------------------------------------------------------- END