Page 1 of 2

V8086 mode hell...

Posted: Wed May 09, 2007 5:33 pm
by pcmattman
(old post irrelevant now)

OK, I've gotten Bochs to recognise the proper segments and made sure CS:EIP is right. Here is the error Bochs thows at me:

Code: Select all

00022978680e[CPU0 ] fetch_raw_descriptor: GDT: index (ff57)1fea > limit (77)
00022978680e[CPU0 ] fetch_raw_descriptor: GDT: index (ff57)1fea > limit (77)
00022978680e[CPU0 ] fetch_raw_descriptor: GDT: index (ff57)1fea > limit (77)
00022978680i[CPU0 ] v8086 mode
00022978680i[CPU0 ] CS.d_b = 16 bit
00022978680i[CPU0 ] SS.d_b = 16 bit
00022978680i[CPU0 ] | EAX=00000000  EBX=00000000  ECX=00000000  EDX=00000000
00022978680i[CPU0 ] | ESP=00081000  EBP=00000000  ESI=00000000  EDI=00000000
00022978680i[CPU0 ] | IOPL=3 id vip vif ac VM RF nt of df IF tf sf zf af pf cf
00022978680i[CPU0 ] | SEG selector     base    limit G D
00022978680i[CPU0 ] | SEG sltr(index|ti|rpl)     base    limit G D
00022978680i[CPU0 ] |  CS:0000( 0001| 0|  3) 00000000 0000ffff 0 0
00022978680i[CPU0 ] |  DS:0015( 0002| 0|  3) 00000150 0000ffff 0 0
00022978680i[CPU0 ] |  SS:0015( 0002| 0|  3) 00000150 0000ffff 0 0
00022978680i[CPU0 ] |  ES:0015( 0002| 0|  3) 00000150 0000ffff 0 0
00022978680i[CPU0 ] |  FS:0015( 0002| 0|  3) 00000150 0000ffff 0 0
00022978680i[CPU0 ] |  GS:0015( 0002| 0|  3) 00000150 0000ffff 0 0
00022978680i[CPU0 ] | EIP=00001024 (00001024)
00022978680i[CPU0 ] | CR0=0x00000011 CR1=0 CR2=0x00000000
00022978680i[CPU0 ] | CR3=0x00000000 CR4=0x00000000
00022978680i[CPU0 ] >> jmp .+0xfffe (0x00001024) : EBFE
00022978680e[CPU0 ] exception(): 3rd (10) exception with no resolution, shutdown status is 00h, resetting
Any ideas? I've been at this for a day now...

Posted: Thu May 10, 2007 12:15 am
by Combuster
Once again, have you checked the contents of IDT, TSS and GDT and asserted they were correct? What are the circumstances under which the error occurs? How can it be possible that a jump in unprotected mode causes an exception?

I think its time to ask yourself the big questions. We don't have a crystal ball to look in - you know more about your code than we do.

Posted: Thu May 10, 2007 12:23 am
by pcmattman
Yes, I have checked the contents of the IDT, the TSS and the GDT. That's the first thing I did.

And just as I was about to say what I thought was right, I found out I was wrong. The problem occurs when I try to switch out of the virtual mode task. If I disable interrupts (via EFLAGS) in the new task I have no problems.

The one thing I didn't try... Now, when a task returns, the TSS is used to get the SS0:ESP0 data? Does there have to be data on the stack referenced in that field, or can it be empty?

Posted: Thu May 10, 2007 12:36 am
by Combuster
when using a hardware task switch for interrupt handling, the SSx and ESPx fields are ignored. Instead they are directly taken from the current SS and ESP fields in that TSS.
When switching privilege levels within the same task, SSx and ESPx are read as expected.

Now, what do you mean with 'a task returns' ?

Posted: Thu May 10, 2007 12:41 am
by pcmattman
OK, here's what's happening:
  • Task switch --> virtual mode task (via stack switch, so IRQ-based)
  • VM task executes...
  • IRQ fires
  • Triple fault
Here's my IRQ entry code:

Code: Select all

# handles irq calls - two bytes already pushed - error code and interrupt number
irq_common_stub:

	# push all general registers (eax et al)
	pusha

	# push segments
	push %ds
	push %es
	push %fs
	push %gs

	# use kernel segments
	movw $0x10,%ax
	movw %ax,%ds
	movw %ax,%es
	movw %ax,%fs
	movw %ax,%gs
	
	# save the current task's esp, also you could store the kstack, cr3 etc...
	movl %esp, (_current_task)

	# push ESP (more like save it)
	movl %esp,%eax
	push %eax

	# call the IRQ handler
	call _irq_handler

	# restore EAX/ESP
	pop %eax
	
	# restore the new stack pointer
	movl (_current_task),%esp

	# restore segments
	pop %gs
	pop %fs
	pop %es
	pop %ds

	# restore general registers
	popa

	# skip over intnum and errcode
	add $8,%esp

	# return
	iret
The SS:ESP fields in the TSS are set, as are the SS0:ESP0 fields.

Virtual mode tasks run in IOPL-3, kernel in IOPL-0.

Edit:
The processor services a hardware interrupt generated to signal the suspension
of execution of the virtual-8086 application. This hardware interrupt may be
generated by a timer or other external mechanism. Upon receiving the hardware
interrupt, the processor enters protected mode and switches to a protected-mode
(or another virtual-8086 mode) task either through a task gate in the protectedmode
IDT or through a trap or interrupt gate that points to a handler that initiates
a task switch. A task switch from a virtual-8086 task to another task loads the
EFLAGS register from the TSS of the new task. The value of the VM flag in the new
EFLAGS determines if the new task executes in virtual-8086 mode or not.
Doesn't this mean that it'll use my PMode IDT to service hardware interrupts? I only have one TSS, how does this relate? I'm really confused, and the Intel manuals have done nothing but make me more confused.

How do I get back into kernel code from my virtual mode task?

Edit 2: OK, the only problem now is getting back to ring0. How I do this is beyond me, I had enough trouble figuring out how to get into v8086 mode... :?

Posted: Thu May 10, 2007 2:53 am
by crackers
pcmattman wrote: I only have one TSS, how does this relate?
Only one ? Should'nt you have at least two (one for V86 task and one for kernel) ?

Posted: Thu May 10, 2007 4:04 am
by Pype.Clicker
pcmattman wrote: The one thing I didn't try... Now, when a task returns, the TSS is used to get the SS0:ESP0 data? Does there have to be data on the stack referenced in that field, or can it be empty?
Somehow, that makes me thing you're using task gates in your interrupt table, aren't you. If you are, make sure you keep the TSS untouched at all price, e.g. allocate a "dumb" TSS descriptor and LTR it before you do anything else. That way, when you return to the interrupted task, your TSS descriptor will still contain the proper "start an interrupt" state. That's either this or making sure the state you left in the interrupt task (e.g. IRET) is immediately followed by code that restores the CPU state and process a new interrupt (e.g. process: pusha, code, popa, IRET, jmp process)

Posted: Thu May 10, 2007 5:28 am
by pcmattman
No, I don't use task gates, unless task gates are what Bran's OS development tutorial kernel implements.

I have one TSS, loaded in the GDT. All it has is the SS0:ESP0 fields loaded with a kernel stack pointer.

I just uploaded the newest code to my CVS repository on Sourceforge. This way you can look through the code and not have to make inferences based on what I say.

Posted: Thu May 10, 2007 5:52 am
by Brendan
Hi,

I haven't done anything with V8086 mode for many many years, but IIRC when an IRQ occurs while V8086 code is running the IRQ handler starts with real mode style data segment registers.

For example, if you've got some real mode code like this:

Code: Select all

    mov ax,0xB800
    mov ds,ax
    mov ax,0x1234
    mov es,ax
Then if you get an IRQ, the CPU switches to protected mode and starts the IRQ handler. In this case your IRQ handler does:

Code: Select all

    push ds      ; push 0xB800 on the stack
    push es      ; push 0x1234 on the stack

   ;* more stuff *

    pop es        ;Try to load 0x1234 into a protected mode segment register (CRASH)
    pop ds        ;Try to load 0xB800 into a protected mode segment register (CRASH)
To prevent this problem the CPU automatically saves and restores the V8086 segment registers on the IRQ handler's stack, so that the protected mode code doesn't need to save/restore real mode style segments.


Cheers

Brendan

Posted: Thu May 10, 2007 3:06 pm
by pcmattman
That makes sense. I am pushing/popping the segments each time an IRQ fires, so that could be the cause of the problem.

Is there any way to detect if I'm in real mode (so I know not to push the segments)?

Posted: Thu May 10, 2007 3:50 pm
by Brendan
Hi,
pcmattman wrote:Is there any way to detect if I'm in real mode (so I know not to push the segments)?
If you are in V8086 mode then the VM flag in EFLAGS will be set. If you interrupted V8086 mode code, then the copy of EFLAGS pushed on the interrupt handler's stack will have the VM flag set.


Cheers,

Brendan

Posted: Thu May 10, 2007 4:03 pm
by Combuster
That makes sense. I am pushing/popping the segments each time an IRQ fires, so that could be the cause of the problem.
00022978680i[CPU0 ] v8086 mode
00022978680i[CPU0 ] CS.d_b = 16 bit
00022978680i[CPU0 ] SS.d_b = 16 bit
The idea is nice, but it doesnt tell us why the fault happens in (the switch from) V8086 mode rather than in protected mode :roll:

Posted: Thu May 10, 2007 4:19 pm
by pcmattman
Hmm... It seems that my hard drive detection code fries it. I took out the hard drive detection code and all of a sudden I have no problem... Any ideas why this happens? Should I be handling an IRQ that the hard drive fires (I haven't yet read any data from the disk, just ran IDENTIFY DEVICE)?

Edit: OK, now I handle hard drive IRQs. Still, whenever the device identifcation occurs, I get problems with the virtual mode task... Also, in the virtual mode task no interrupts work, ie. interrupt 10 for BIOS teletype output... Aren't I meant to be able to run the interrupts from within virtual mode, or do I have to do something else before I can call BIOS interrupts?

Edit 2: Wasn't thinking when I disabled drive detection. I sort of need it for my ReadSector function, which is used to read in the virtual mode task from the drive :oops:... I'm still playing around with ideas though, so hopefully I'll get it soon.

Edit 3: This is getting really frustrating! I thought I had it when I learnt (a bit late :shock:) that the segment descriptors in protected mode point to a selector in the GDT. No one ever told me that!

After modifying the whole thing so the segments are correct, I am still not even close to getting this to work, I still have the errors come. If I remove the VM flag from the eflags register, the code executes and then throws a GPF. Put it back in, *bang*!

Any ideas?

Posted: Fri May 11, 2007 1:05 am
by crackers
pcmattman wrote: Also, in the virtual mode task no interrupts work, ie. interrupt 10 for BIOS teletype output... Aren't I meant to be able to run the interrupts from within virtual mode, or do I have to do something else before I can call BIOS interrupts?
Intel Manual wrote: Method 5 software interrupt handling provides a streamlined method of redirecting software interrupts (invoked with the INT n instruction) that occur in virtual 8086 mode back to the 8086 program’s interrupt vector table and its interrupt handlers. Method 5 handling is enabled when the VME flag is set to 1, the IOPL value is 3, and the bit for the interrupt vector in the redirection bit map is set to 0.

Posted: Fri May 11, 2007 1:13 am
by Aali
what i use is i have separate entry points for vm86 and "regular" tasks and the first asm ISR stub checks eflags for the vm flag and jumps to another stub if its set (since ss is loaded from the TSS, i can safely access the stack without reloading segment registers)

if it is a vm86 task, i load the kernel data segment into ds and es, push a pointer to the current stack, enable interrupts (of course, you shouldn't do this if you dont support in-kernel interrupts) and call the C function to handle vm86 interrupts

the non-vm86 stub saves ds and es, loads kernel data registers, saves the 'fastcall' status (sysenter,syscall or softint, mostly used for debugging), checks saved cs if its a userspace or in-kernel interrupt, enables interrupts (again, optional) and calls the relevant C function

this way, the two register structures look kind-of different for a regular task compared to a vm86 task, but thats okay, since they're not handled by the same code

when returning to the process, i check everything in C code and jump to one of four possible asm stubs; vm86, softint, sysexit or sysret