IRETting to V86 mode and NT flag

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
awik
Member
Member
Posts: 41
Joined: Sat Sep 19, 2020 7:18 am

IRETting to V86 mode and NT flag

Post by awik »

Hi all,

I'm trying to figure out why my IRET to V86 mode requires the NT flag to be clear. I can't find any mention of this in the "AMD64 Architecture Programmer's Manual".

It (the manual) says that the NT flag has to be *set* in order to switch tasks with IRET. However, I'm not trying to switch to another task, but the same task (same TSS).

I get an Invalid TSS exception if I don't clear the NT flag. Maybe it is trying to switch tasks, and it (a) finds the target task selector is the same as the currently executing task, or it (b) finds that the previous task link in the TSS is null (0), or it (c) finds the TSS is "busy"?

Code: Select all

	;
	; Set the current task to our TSS selector.
	;
	; Note that this operation causes the TSS in the GDT to
	; be marked as "busy".
	;
	mov	ax, 0x38
	ltr	ax
	;
	; Set up stack for "return" to V86 mode.
	;
	mov	edx,esp
	xor	eax,eax
	push	eax		; dummy, unused
	movzx	eax,word [bp+BSS_OLD_SEG]
	push	eax		; GS
	push	eax		; FS
	push	eax		; DS
	push	eax		; ES
	push	eax		; SS
	push	edx		; ESP
%define FLAGS_NT_BIT   14
%define FLAGS_VM_BIT   17  ; hex 11h
%define FLAGS_IOPL_BIT 12  ; actually 12 *and* 13.
%define FLAGS_IOPL_3 (3 << FLAGS_IOPL_BIT)
%define FLAGS_NT     (1 << FLAGS_NT_BIT)
%define FLAGS_VM     (1 << FLAGS_VM_BIT)
	pushfd
	pop	edx
	or	edx, FLAGS_VM | FLAGS_IOPL_3
	push	edx		; EFLAGS
	push	eax		; CS
	push	dword .v86_target_eip
	;
	; Apparently, the NT flag has to be clear, or we get an Invalid
	; TSS exception.
	;
	pushfd
	pop	edx
	and	edx, ~FLAGS_NT
	push	edx
	popfd
	;
	iretd			; "return" to V86 mode.
;--------
BITS 16
.v86_target_eip:
int3
MichaelPetch
Member
Member
Posts: 774
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: IRETting to V86 mode and NT flag

Post by MichaelPetch »

What Is the context of this code? Are you trying to enter v8086 mode and don't intend to use hardware task switching? Or are you using hardware task switching and you are already in a task and are trying to switch to another? If you are in protected mode and NT=1 IRET will look at the previous link field in the current TSS and do a task switch to it and then set the original (the one that did the IRET) task's state to busy. If NT=1 and there is no valid task in the previous task link of the TSS then that will cause problems.

The hardware task switching mechanism of the x86 doesn't allow tasks to be re-entrant, only nested. You can't use an IRET with NT=1 with a previous link of the same task because the processor will not allow you to switch to a task that is already busy.

If you are just trying to enter v8086 mode and aren't using hardware task switching (using things like call and jump to task gates etc) then don't use PUSHFD/POPFD to get the current EFLAGS state. Start from scratch and make your own EFLAGS with NT=0, VM=1, your IOPL bits set as needed (3 in your case), and I usually set bit 1 on as it is supposed to be reserved and always contain a value of 1. If you want interrupts on when v8086 mode starts then set IF=1 as well.
awik
Member
Member
Posts: 41
Joined: Sat Sep 19, 2020 7:18 am

Re: IRETting to V86 mode and NT flag

Post by awik »

MichaelPetch wrote:What Is the context of this code?
At the start of the quoted code, the processor is running in 32-bit protected mode. At the end, the processor should be in V86 mode.
Are you trying to enter v8086 mode and don't intend to use hardware task switching?
Correct.
Or are you using hardware task switching and you are already in a task and are trying to switch to another?
I'm not trying to switch tasks, but to continue execution in V86 mode in the context on the same task, using the TSS I loaded with LTR at the start of the quoted code.
If you are in protected mode and NT=1 IRET will look at the previous link field in the current TSS and do a task switch to it and then set the original (the one that did the IRET) task's state to busy.
And if the previous task link is null, it will generate an Invalid TSS exception?

-Albert.
MichaelPetch
Member
Member
Posts: 774
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: IRETting to V86 mode and NT flag

Post by MichaelPetch »

Yes, if you have a NULL selector as the previous link it will raise an Invalid TSS Exception. See the Intel Software Development Manual Volume 3. The Invalid TSS exception (see table 6-6) gets thrown under a variety of circumstances including:
During an IRET task switch, the backlink is a NULL selector.
It sounds as if the NT flag in your EFLAGS was originally set so when you propagated that to the EFLAGS you used for the IRETD, it caused an exception because you had no proper backlink. Just put your own new EFLAGS on the stack with the values you want (NT=0, VM=1, IOPL=3 etc). You can replace:

Code: Select all

   pushfd
   pop   edx
   or   edx, FLAGS_VM | FLAGS_IOPL_3
   push   edx      ; EFLAGS
with simply:

Code: Select all

   push dword FLAGS_VM | FLAGS_IOPL_3 | 1<<1
The '1<<1' is there to set bit 1 which is a reserved EFLAGS bit which is supposed to always be the value 1. You can then get rid of this code:

Code: Select all

   pushfd
   pop   edx
   and   edx, ~FLAGS_NT
   push   edx
   popfd
and replace it with code that sets your current EFLAGS to a well known state:

Code: Select all

   push dword 1<<1
   popfd
VM=0, IF=0, IOPL=0, Reserved Bit=1. It might be useful to put this code early on in your assembly code.
Last edited by MichaelPetch on Wed May 01, 2024 6:33 am, edited 2 times in total.
awik
Member
Member
Posts: 41
Joined: Sat Sep 19, 2020 7:18 am

Re: IRETting to V86 mode and NT flag

Post by awik »

MichaelPetch wrote:Yes, if you have a NULL selector as the previous link it will raise an Invalid TSS Exception. See the Intel Software Development Manual Volume 3. The Invalid TSS exception (see table 6-6) gets thrown under a variety of circumstances including:
During an IRET task switch, the backlink is a NULL selector.
Thanks, that's what I was looking for.
It sounds as if the NT flag in your EFLAGS was originally set
Yes it is, for some unknown reason.
so when you propagated that to the EFLAGS you used for the IRETD,
After some more testing, I've figured out that it doesn't matter if NT is set in the flags used for IRET.
it caused an exception because you had no proper backlink.
Right. That was what I wanted to know.
Just put your own new EFLAGS on the stack with the values you want (NT=0, VM=1, IOPL=3 etc). You can replace:

Code: Select all

   pushfd
   pop   edx
   or   edx, FLAGS_VM | FLAGS_IOPL_3
   push   edx      ; EFLAGS
with simply:

Code: Select all

   push dword FLAGS_VM | FLAGS_IOPL_3 | 1<<1
The '1<<1' is there to set bit 1 which is a reserved EFLAGS bit which is supposed to always be the value 1. You can then get rid of this code:

Code: Select all

   pushfd
   pop   edx
   and   edx, ~FLAGS_NT
   push   edx
   popfd
I still need to clear the NT flag before IRETD. That's what's important, not whether it gets passed to the VM.

-Albert.
MichaelPetch
Member
Member
Posts: 774
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: IRETting to V86 mode and NT flag

Post by MichaelPetch »

Yeah I meant to go back and add the replacement code that just sets the current EFLAGS to a known useful state rather than use the unknown values (including NT) in the current EFLAGS. I updated my answer for future readers.

It is a curiosity though that the NT flag is actually set at all. Is it possible that outside of the code you show us you set EFLAGS incorrectly? I'd be curious to see all your test code.
Last edited by MichaelPetch on Wed May 01, 2024 7:05 am, edited 1 time in total.
nullplan
Member
Member
Posts: 1760
Joined: Wed Aug 30, 2017 8:24 am

Re: IRETting to V86 mode and NT flag

Post by nullplan »

awik wrote:Yes it is, for some unknown reason.
The processor sets the NT bit itself if a task gate is executed. Task gates can be in the GDT or IDT. In the GDT they get called with a far call instruction, and in the IDT they get invoked with an interrupt. In 32-bit mode, task gates are the only way known to me to force ESP to a valid value even if the interrupt occurs in kernel-mode, and are therefore the only good way I know how to deal with NMI, machine check exception, and double fault.

If you have task gates in your IDT or GDT, and are not actually planning on using hardware task switching, you should make the handlers manually restore the normal system state. Linux has some code for that; they reload the TR with the value it is supposed to have, clear the busy flag on the TSS for the interrupt task, can clear the NT bit from EFLAGS. They also construct a normal interrupt stack frame before proceeding.

If you do not use task gates, then there can't be that many places where you store arbitrary values in EFLAGS, right? The only instructions that do that are POPFD and IRETD.
awik wrote:After some more testing, I've figured out that it doesn't matter if NT is set in the flags used for IRET.
If you had read the documentation, you wouldn't have had to guess. EFLAGS.NT affects the operation of IRETD to such an extent that if it is set, the stack image is ignored.
Carpe diem!
awik
Member
Member
Posts: 41
Joined: Sat Sep 19, 2020 7:18 am

Re: IRETting to V86 mode and NT flag

Post by awik »

nullplan wrote:If you do not use task gates, then there can't be that many places where you store arbitrary values in EFLAGS, right? The only instructions that do that are POPFD and IRETD.
Yes, but wherever the flag does get set, it's not done by my code.
awik wrote:After some more testing, I've figured out that it doesn't matter if NT is set in the flags used for IRET.
If you had read the documentation, you wouldn't have had to guess.
Reading documentation does not necessarily imply you remember what you've read, and moreover, you do not always understand the full ramifications of what you do remember.

I've read about exceptions and task management very recently, from "AMD64 Architecture Programmer's Manual", and it is not exactly clear on this matter. I guess you should be able to read between the lines, but that is easy to say in hindsight.
EFLAGS.NT affects the operation of IRETD to such an extent that if it is set, the stack image is ignored.
Yes, that's beginning to dawn on me now.

-Albert.
MichaelPetch
Member
Member
Posts: 774
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: IRETting to V86 mode and NT flag

Post by MichaelPetch »

awik wrote:Yes, but wherever the flag does get set, it's not done by my code.
I'm very curious about your code. I feel like there is something fishy going on here and it is highly unusual the NT flag would be set in EFLAGS under most conditions unless some kind of task gates were used OR there was a bug that reloaded the EFLAGS with the wrong value. Do you use someone else's bootloader? Multiboot/Grub? What environment do you run from? An emulator like QEMU/BOCHS? Real hardware?

Is it possible to post all of your code? I ask this because it is unusual enough that NT is set that suggests there *might( be a bug somewhere else. You of course can put your own values into EFLAGS (as I suggested in my updated comment earlier) with NT turned off, but why it was on to begin with is mysterious.

Here is some code that enters protected mode and then v8086 mode (with IOPL=3) in a legacy bootloader (note: the A20 switch isn't robust so might not work on all real hardware, and lacks a BPB). It will prints something to the display. You should be able to run it on BOCHS/QEMU and most real hardware emulating USB FDD (floppy):

Code: Select all

; Assemble with
;     nasm -f bin boot.asm -o boot.bin

VIDEO_TEXT_ADDR        EQU 0xb8000 ; Hard code beginning of text video memory
ATTR_BWHITE_ON_GREEN   EQU 0x2f    ; Bright white on green attribute
ATTR_BWHITE_ON_MAGENTA EQU 0x5f    ; Bright White on magenta attribute

PM_MODE_STACK          EQU 0x80000 ; Protected mode stack below EBDA

V86_STACK_SEG          EQU 0x0000  ; v8086 stack SS
V86_STACK_OFS          EQU 0x0000  ; v8086 stack SP
V86_CS_SEG             EQU 0x0000  ; v8086 code segment CS

EFLAGS_VM_BIT          EQU 17      ; EFLAGS VM bit
EFLAGS_BIT1            EQU 1       ; EFLAGS bit 1 (reserved, always 1)
EFLAGS_IF_BIT          EQU 9       ; EFLAGS IF bit
EFLAGS_IOPL_BITS       EQU 12      ; EFLAGS IOPL bits 12-13
IOPL                   EQU 3       ; v8086 IOPL = 3

TSS_IO_BITMAP_SIZE     EQU 256/8   ; IO Bitmap for 256 IO ports
                                   ; Size 0 disables IO port bitmap (no permission)

; Macro to build a GDT descriptor entry
%define MAKE_GDT_DESC(base, limit, access, flags) \
    (((base & 0x00FFFFFF) << 16) | \
    ((base & 0xFF000000) << 32) | \
    (limit & 0x0000FFFF) | \
    ((limit & 0x000F0000) << 32) | \
    ((access & 0xFF) << 40) | \
    ((flags & 0x0F) << 52))

bits 16
ORG 0x7c00

; Include a BPB (1.44MB floppy with FAT12) to be more compatible with USB floppy media
global bpb_disk_info

    jmp short boot_continue
    nop

bpb_disk_info:

    ; Dos 4.0 EBPB 1.44MB floppy
    OEMname:           db    "mkfs.fat"  ; mkfs.fat is what OEMname mkdosfs uses
    bytesPerSector:    dw    512
    sectPerCluster:    db    1
    reservedSectors:   dw    1
    numFAT:            db    2
    numRootDirEntries: dw    224
    numSectors:        dw    2880
    mediaType:         db    0xf0
    numFATsectors:     dw    9
    sectorsPerTrack:   dw    18
    numHeads:          dw    2
    numHiddenSectors:  dd    0
    numSectorsHuge:    dd    0
    driveNum:          db    0
    reserved:          db    0
    signature:         db    0x29
    volumeID:          dd    0x2d7e5a1a
    volumeLabel:       db    "NO NAME    "
    fileSysType:       db    "FAT12   "

boot_continue:
    cli                         ; Disable interrupts for this demo
    xor ax, ax                  ; DS=SS=ES=0
    mov ds, ax
    mov es, ax
    mov ss, ax                  ; Stack at 0x0000:0x7c00
    mov sp, 0x7c00
    cld                         ; Set string instructions to use forward movement

    ; Fast method of enabling A20 may not work on all x86 BIOSes
    ; It is good enough for emulators and most modern BIOSes
    ; See: https://wiki.osdev.org/A20_Line
    in al, 0x92
    or al, 2
    out 0x92, al                ; Enable A20 using Fast Method

    lgdt [gdt.gdtr]             ; Load our GDT

    mov eax, cr0
    or eax, 1
    mov cr0, eax                ; Set protected mode flag
    jmp CODE32_SEL:start32      ; FAR JMP to set CS

; v8086 code entry point
v86_mode_entry:
    sub dword [vidmem_ptr], VIDEO_TEXT_ADDR
                                ; Adjust video pointer to be relative to beginning of
                                ;     segment 0xb800

    mov si, in_v86_msg          ; Print in v86 message
    mov ah, ATTR_BWHITE_ON_MAGENTA
                                ; Attribute to print with
    call print_string_rm_nobios

.endloop:
    jmp $                       ; Infinite loop since we didn't code a solution to exit VM

; Function: print_string_rm_nobios
;           Display a string to the console on display page 0 in real/v8086 mode
;           without using the BIOS. We don't have a proper v8086 monitor so can't
;           use BIOS to display.
;
;           Very basic. Doesn't update hardware cursor, doesn't handle scrolling,
;           LF, CR, TAB.
;
; Inputs:   SI  = Offset of address to print
;           AH  = Attribute of string to print
; Clobbers: None
; Returns:  None

print_string_rm_nobios:
    push di
    push si
    push ax
    push es

    mov di, VIDEO_TEXT_ADDR>>4  ; ES=0xb800 (text video mode segment)
    mov es, di

    mov di, [vidmem_ptr]        ; Start from video address stored at vidmem_ptr
    jmp .getchar
.outchar:
    stosw                       ; Output character to display
.getchar:
    lodsb                       ; Load next character from string
    test al, al                 ; Is character NUL?
    jne .outchar                ; If not, go output character

    mov [vidmem_ptr], di        ; Update global video pointer

    pop es
    pop ax
    pop si
    pop di
    ret

; 32-bit protected mode entry point
bits 32
start32:
    mov ax, DATA32_SEL          ; Setup the segment registers with data selector
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov esp, PM_MODE_STACK      ; Set protected mode stack pointer

    mov fs, ax                  ; Not currently using FS and GS
    mov gs, ax

    mov ah, ATTR_BWHITE_ON_GREEN; Attribute to print with
    mov al, ah                  ; Attribute to clear last line when scrolling
    mov esi, in_pm_msg          ; Print message that we are in protected mode
    call print_string_pm

    mov ecx, tss_struct_size    ; Zero out entire TSS structure
    mov edi, tss0
    xor eax, eax
    rep stosb

    mov eax, tss0
    ; Set iomap_base in tss with the offset of the iomap relative to beginning of the tss
    mov word [eax + tss_struct.iomap_base], tss_struct.iomap
%if TSS_IO_BITMAP_SIZE > 0
    mov byte [eax + tss_struct.iomap_pad], 0xff
%endif
    ; Update TSS entry in GDT with base of the TSS (tss0)
    mov [gdt.tss32_0 + 2], ax
    shr eax, 16
    mov [gdt.tss32_0 + 4], al
    mov [gdt.tss32_0 + 7], ah

    mov eax, TSS32_0_SEL
    ltr ax                      ; Load default TSS (used for exceptions, interrupts, etc)

    xor ebx, ebx                ; EBX=0
    push ebx                    ; Real mode GS=0
    push ebx                    ; Real mode FS=0
    push ebx                    ; Real mode DS=0
    push ebx                    ; Real mode ES=0
    push V86_STACK_SEG
    push V86_STACK_OFS          ; v8086 stack SS:SP (grows down from SS:SP)
    push dword 1<<EFLAGS_VM_BIT | 1<<EFLAGS_BIT1 | IOPL<<EFLAGS_IOPL_BITS
                                ; Set VM Bit, IF bit is off, DF=0(forward direction),
                                ; IOPL=0, Reserved bit (bit 1) always 1. Everything
                                ; else 0. These flags will be loaded in the v8086 mode
                                ; during the IRET. We don't want interrupts enabled
                                ; because we have no v86 monitor via protected mode
                                ; GPF handler
    push V86_CS_SEG             ; Real Mode CS (segment)
    push v86_mode_entry         ; Entry point (offset)
    iret                        ; Transfer control to v8086 mode and our real mode code

; Function: print_string_pm
;           Display a string to the console on display page 0 in protected mode.
;           Very basic. Doesn't update hardware cursor, doesn't handle scrolling,
;           LF, CR, TAB.
;
; Inputs:   ESI = Offset of address to print
;           AH  = Attribute of string to print
; Clobbers: None
; Returns:  None

print_string_pm:
    push edi
    push esi
    push eax

    mov edi, [vidmem_ptr]       ; Start from video address stored at vidmem_ptr
    jmp .getchar
.outchar:
    stosw                       ; Output character to video display
.getchar:
    lodsb                       ; Load next character from string
    test al, al                 ; Is character NUL?
    jne .outchar                ;     If not, go back and output character

    mov [vidmem_ptr], edi       ; Update global video pointer
    pop eax
    pop esi
    pop edi
    ret

align 4
vidmem_ptr: dd VIDEO_TEXT_ADDR  ; Start console output in upper left of display

in_pm_msg:
    db "In 32-bit protected mode!", 0
in_v86_msg:
    db "In v8086 mode!", 0

align 4
gdt:
CODE32_SEL  equ .code32  - .start
DATA32_SEL  equ .data32  - .start
TSS32_0_SEL equ .tss32_0 - .start

.start:
.null:
    dq MAKE_GDT_DESC(0, 0, 0, 0) ; null descriptor
.code32:
    dq MAKE_GDT_DESC(0, 0x000fffff, 10011010b, 1100b)
                                ; 32-bit code, 4kb gran, limit 0xffffffff bytes, base=0
.data32:
    dq MAKE_GDT_DESC(0, 0x000fffff, 10010010b, 1100b)
                                ; 32-bit data, 4kb gran, limit 0xffffffff bytes, base=0
.tss32_0:
    dq MAKE_GDT_DESC(0, tss_struct_size-1, 10001001b, 0000b)
                                ; 32-bit TSS, 1b gran, available, IOPL=0
.end:

.gdtr:
    dw .end - .start - 1
                                ; limit (Size of GDT - 1)
    dd .start                   ; base of GDT

; Pad boot sector to 510 bytes and add 2 byte boot signature
TIMES 510-($-$$) db  0
dw 0xaa55

struc tss_struct
.back_link: resd 1
.esp0:      resd 1              ; Kernel stack pointer used on ring transitions
.ss0:       resd 1              ; Kernel stack segment used on ring transitions
.esp1:      resd 1
.ss1:       resd 1
.esp2:      resd 1
.ss2:       resd 1
.cr3:       resd 1
.eip:       resd 1
.eflags:    resd 1
.eax:       resd 1
.ecx:       resd 1
.edx:       resd 1
.ebx:       resd 1
.esp:       resd 1
.ebp:       resd 1
.esi:       resd 1
.edi:       resd 1
.es:        resd 1
.cs:        resd 1
.ss:        resd 1
.ds:        resd 1
.fs:        resd 1
.gs:        resd 1
.ldt:       resd 1
.trap:      resw 1
.iomap_base:resw 1              ; IOPB offset

;.cetssp:    resd 1             ; Need this if CET is enabled

; Insert any kernel defined task instance data here
; ...

; If using VME (Virtual Mode extensions) there need to bean additional 32 bytes
; available immediately preceding iomap. If using VME uncomment next 2 lines
;.vmeintmap:                     ; If VME enabled uncomment this line and the next
;    resb 32                     ;     32*8 bits = 256 bits (one bit for each interrupt)

.iomap: resb TSS_IO_BITMAP_SIZE ; IO bitmap (IOPB) size 8192 (8*8192=65536) representing
                                ; all ports. An IO bitmap size of 0 would fault all IO
                                ; port access if IOPL < CPL (CPL=3 with v8086)
%if TSS_IO_BITMAP_SIZE > 0
.iomap_pad: resb 1              ; Padding byte that has to be filled with 0xff
                                ; To deal with issues on some CPUs when using an IOPB
%endif
endstruc

section .bss
tss0: resb tss_struct_size

If this code fails to get into v8086 mode because NT was set previously then it would have to have been a result of the BIOS leaving it in that state.
awik
Member
Member
Posts: 41
Joined: Sat Sep 19, 2020 7:18 am

Re: IRETting to V86 mode and NT flag

Post by awik »

MichaelPetch wrote:
awik wrote:Yes, but wherever the flag does get set, it's not done by my code.
I'm very curious about your code. I feel like there is something fishy going on here and it is highly unusual the NT flag would be set in EFLAGS under most conditions unless some kind of task gates were used OR there was a bug that reloaded the EFLAGS with the wrong value. Do you use someone else's bootloader? Multiboot/Grub? What environment do you run from? An emulator like QEMU/BOCHS? Real hardware?

Is it possible to post all of your code?
I made a ZIP file: v86.zip.

It runs as a DOS .COM file, so it doesn't need a boot loader. Most recently, i've been using DOSBox-X as main development platform. When I first wrote it, I was using VMware. I've also used a largely similar program with Bochs.

What it does, in summary, is to enable V86 mode and return to DOS with the terminate-and-stay-resident (TSR) system call. It provides visual feedback using direct writes to text mode video memory.

If it seems to freeze, it may help to press (and release) the ESC key.
I ask this because it is unusual enough that NT is set that suggests there *might( be a bug somewhere else. You of course can put your own values into EFLAGS (as I suggested in my updated comment earlier) with NT turned off, but why it was on to begin with is mysterious.
I used the DOSBox-X debugger to examine this, and upon startup, the flag was not set. Then I mounted a C: drive, and checked again, and now the flag was set.

-Albert.
MichaelPetch
Member
Member
Posts: 774
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: IRETting to V86 mode and NT flag

Post by MichaelPetch »

awik wrote: I made a ZIP file: v86.zip.

It runs as a DOS .COM file, so it doesn't need a boot loader. Most recently, i've been using DOSBox-X as main development platform. When I first wrote it, I was using VMware. I've also used a largely similar program with Bochs.
Ah it is under DOS, yeah then maybe there is some previous task switching DosBox did or a DOS programs did (or a driver). Thanks for the Zip when I get a chance later I'll take a look at it.
Post Reply