Realmode Switch Being Nasty

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.
User avatar
Creature
Member
Member
Posts: 548
Joined: Sat Dec 27, 2008 2:34 pm
Location: Belgium

Realmode Switch Being Nasty

Post by Creature »

Hello,

At the risk of being called a newbie, I decided to post here about my code that switches to realmode. I've tried to follow the Intel Manuals as good as possible, but my code keeps breaking. I'm fairly new to doing the switches so I thought it'd be better to let you guys take a look at it than stay stuck with it.

Here it goes:

Code: Select all

[BITS 32]

[ORG 0x6000]

RegsAX dw 0							; |
RegsBX dw 0							; | > These will be used later, they are ignored at this time.
RegsCX dw 0							; | > 
RegsDX dw 0							; |

; This should be at address 0x6008 (which I'm calling).
Entry:	
	; Step 1
	cli
	
	; Step 2: Turn paging off...
	mov eax, CR0
	and eax, 0x7FFFFFFF
	mov CR0, eax
	
	mov eax, 0
	mov CR3, eax
	
	; Step 3
	jmp 0x08:ProtectedMode16

[BITS 16]

ProtectedMode16:
	; Step 4 - 5
	mov ax, 0x10
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax

	; Step 6
	lidt [RealModeIDT]
	
	; Step 7: Turn protected mode off...
	mov eax, CR0
	and eax, 0xFFFFFFFE
	mov CR0, eax
	
	; Step 8
	jmp 0x00:RealMode
    
RealMode:
	; Step 9
	xor eax, eax
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax
	
	; Step 10
	sti
	
	; Perform a BIOS interrupt.
	mov ax, 13h
	int 10h
	
	cli

	; Code to switch back is unfinished, but not really necessary at this time.

	jmp $

RealModeIDT:
	dw 0x03FF
	dd 0x0000
As you can see, I'm trying to create a realmode switching program that switches to graphics mode (just for testing). However, Bochs either gives me several errors (after a triple fault), such as

Code: Select all

00017728222e[CPU0 ] SetCR0: GP(0) when attempt to set CR0.NW with CR0.CD cleared !
00017728222e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
00017728222e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)
or, when switching the code around a bit (which I feel can't be right), Bochs tells me the CPU is in realmode with

Code: Select all

[CPU0 ] CS.d_b = 32 bit
[CPU0 ] SS.d_b = 32 bit
and I get no errors or problems in the Bochslog at all, but nothing happens either.

I know there will probably be some serious flaws in my code, but I guess everybody has to learn sometime.

PS: The realmode switching program is assembled as pure binary (-f bin) with NASM and passed to the kernel as a module with GRUB. I then clear 4096 bytes (0x1000 = size of one page) starting at 0x6000 and copy the module to its origin (0x6000). Then, finally, I call the module at 0x6008 (skipping the 4 words). GRUB modules, the kernel, and everything below 1 MB is identity mapped in my kernel.

Thanks in advance for your help,
Creature
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Realmode Switch Being Nasty

Post by Brendan »

Hi,

All of the code you've posted (which doesn't include the GDT, etc) looks entirely correct to me.

However, I am assuming that the "CLI" in Step 1 isn't needed (and that interrupts were disabled already, before you attempt to switch back to real mode).
Creature wrote:As you can see, I'm trying to create a realmode switching program that switches to graphics mode (just for testing). However, Bochs either gives me several errors (after a triple fault), such as
After a triple fault nothing matters. You want the error that occurs before the triple fault.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Creature
Member
Member
Posts: 548
Joined: Sat Dec 27, 2008 2:34 pm
Location: Belgium

Re: Realmode Switch Being Nasty

Post by Creature »

In protected mode, I set up a GDT and IDT and such, but is it also required to create or enable a new GDT once in real-mode (I thought it was unnecessary/unexistant there)?

If it's any help, here is the Bochs log:

Code: Select all

... (All Bochs setup and initialization stuff).
ACPI tables: RSDP addr=0x000fbc80 ACPI DATA addr=0x01ff0000 size=0x988
00002477344i[BIOS ] Firmware waking vector 0x1ff00cc
00002488457i[PCI  ] 440FX PMC write to PAM register 59 (TLB Flush)
00002489301i[BIOS ] bios_table_cur_addr: 0x000fbca4
00002501469i[BIOS ] ata0-0: PCHS=2/16/63 translation=none LCHS=2/16/63
00010151760i[BIOS ] Booting from 07c0:0000
00010264615i[BIOS ] int13_harddisk: function 41, unmapped device for ELDL=81
00010269391i[BIOS ] int13_harddisk: function 08, unmapped device for ELDL=81
00010274041i[BIOS ] *** int 15h function AX=00c0, BX=0000 not yet supported!
00016512077e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x06)
00016512077e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
00016512077e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)
00016512077i[CPU0 ] CPU is in protected mode (active)
00016512077i[CPU0 ] CS.d_b = 32 bit
00016512077i[CPU0 ] SS.d_b = 32 bit
00016512077i[CPU0 ] EFER   = 0x00000000
00016512077i[CPU0 ] | RAX=0000000060000012  RBX=000000000002effc
00016512077i[CPU0 ] | RCX=0000000000000000  RDX=0000000000000000
00016512077i[CPU0 ] | RSP=00000000dfffffa8  RBP=00000000ffffffff
00016512077i[CPU0 ] | RSI=000000000002f4b6  RDI=000000000002f4ac
00016512077i[CPU0 ] |  R8=0000000000000000   R9=0000000000000000
00016512077i[CPU0 ] | R10=0000000000000000  R11=0000000000000000
00016512077i[CPU0 ] | R12=0000000000000000  R13=0000000000000000
00016512077i[CPU0 ] | R14=0000000000000000  R15=0000000000000000
00016512077i[CPU0 ] | IOPL=0 ID vip vif ac vm RF nt of df if tf sf zf af PF cf
00016512077i[CPU0 ] | SEG selector     base    limit G D
00016512077i[CPU0 ] | SEG sltr(index|ti|rpl)     base    limit G D
00016512077i[CPU0 ] |  CS:0008( 0001| 0|  0) 00000000 ffffffff 1 1
00016512077i[CPU0 ] |  DS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00016512077i[CPU0 ] |  SS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00016512077i[CPU0 ] |  ES:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00016512077i[CPU0 ] |  FS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00016512077i[CPU0 ] |  GS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00016512077i[CPU0 ] |  MSR_FS_BASE:0000000000000000
00016512077i[CPU0 ] |  MSR_GS_BASE:0000000000000000
00016512077i[CPU0 ] | RIP=000000000000603c (000000000000603c)
00016512077i[CPU0 ] | CR0=0x60000013 CR2=0x0000000000000000
00016512077i[CPU0 ] | CR3=0x00000000 CR4=0x00000600
00016512077i[CPU0 ] 0x000000000000603c>> (invalid)  : FFFF
00016512077e[CPU0 ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting
00016512077i[SYS  ] bx_pc_system_c::Reset(HARDWARE) called
My protected mode GDT and IDT are still very much like the ones from JamesM's tutorial. If access to the protected mode GDT/IDT code is required (or any other code from the OS itself, as the realmode switching source isn't uploaded), it can pretty much all be found here (Interrupts.cpp and Interrupts.h contain the GDT/IDT/... code and Assembly/Interrupts.asm some more assembly code).

Also a bit of code that isn't uploaded (the routine that actually executes the realmode program):

Code: Select all

	cout.Clear();
	ASMV("cli");

	const Module *RealmodeSwitcher = Kernel.FindModule("/RealMode.bin");

	IdentityMapDir(KernelDir, 0x6000, PAGE_SIZE, true, false);

	memset((void *) 0x6000, 0, PAGE_SIZE);
	memcpy((void *) 0x6000, RealmodeSwitcher->Start, RealmodeSwitcher->Length);

	// This structure is locally defined only for testing purposes.
	struct Test { word AX, BX, CX, DX; };
	Test *Regs = (Test *) 0x6000;
	Regs->AX = 50;
	Regs->BX = 60;
	Regs->CX = 70;
	Regs->DX = 80;
	// When I use the Bochs 0xE9 port to write the values of the regs before I switch to 16-bit protected mode, valid values are written, but if I do it after the switch, nothing happens. But I don't really require the registers to work at this time.

	ASMV("call *%0" : : "r" (0x6008));
On one side, I am happy to hear that the code doesn't contain any serious flaws, but on the other side it makes it even stranger than it doesn't work.

Again, my apologies if I sound like a newbie and thanks for any help,
Creature
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
Hangin10
Member
Member
Posts: 162
Joined: Wed Feb 27, 2008 12:40 am

Re: Realmode Switch Being Nasty

Post by Hangin10 »

In the switch to real mode, you are jumping using 0x08 as a selector to get to 16bit protected mode code, but isn't that the same descriptor as the rest of your protected mode kernel? You need a code descriptor for 16bit code.
User avatar
Creature
Member
Member
Posts: 548
Joined: Sat Dec 27, 2008 2:34 pm
Location: Belgium

Re: Realmode Switch Being Nasty

Post by Creature »

Hangin10 wrote:In the switch to real mode, you are jumping using 0x08 as a selector to get to 16bit protected mode code, but isn't that the same descriptor as the rest of your protected mode kernel? You need a code descriptor for 16bit code.
That's right, but I didn't really know what other descriptor to use or what actually makes a CS 16-bits. I've tried using 0x28 for example, which stops the kernel from triple-faulting, but again makes nothing happen. Bochs then reports this error:

Code: Select all

jump_protected: gate type 11 unsupported
Thanks,
Creature
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
Hangin10
Member
Member
Posts: 162
Joined: Wed Feb 27, 2008 12:40 am

Re: Realmode Switch Being Nasty

Post by Hangin10 »

You need to make a descriptor with the D/B bit clear. Selector 0x28 at the moment is your TSS if I'm counting the lines in your Interrupts.cpp file correctly. Read the manuals for the format of GDT entries.
User avatar
Dex
Member
Member
Posts: 1444
Joined: Fri Jan 27, 2006 12:00 am
Contact:

Re: Realmode Switch Being Nasty

Post by Dex »

Here is a working demo, if it will helps http://www.dex4u.com/demos/VesaDemo.zip
User avatar
Creature
Member
Member
Posts: 548
Joined: Sat Dec 27, 2008 2:34 pm
Location: Belgium

Re: Realmode Switch Being Nasty

Post by Creature »

I think I'm still having trouble understanding the theory behind it, I've been digging through the Intel Manuals for a while now.

I've found the lay-out of the segment descriptors and the D/B bit, but I'm not sure where the CS comes in with the lay-out of a segment descriptor. I'm referring to the fact that I apparently need to set at max 8 bits (such as in 0x28, the higher bits are zero), but I'm not sure what part CS is of the segment descriptor, is it bits 16 - 24? If that is so, that would lead me to make a CS of 0x8F instead of 0xCF which leaves the D/B bit at 0.

Please excuse my confusion, there's probably an important piece of the puzzle missing that will make me understand it all.

Creature
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
Hangin10
Member
Member
Posts: 162
Joined: Wed Feb 27, 2008 12:40 am

Re: Realmode Switch Being Nasty

Post by Hangin10 »

The CS register (the value is a selector) just points to a GDT entry. The entry (a descriptor) needs the D/B bit to be clear, so in your calls to the function that sets up a GDT entry, you need the 0xCF to be 0x0F (I'm not sure if a 16bit code segment can have granularity be 1, you don't need it here anyway, as real mode code can't be >1MB).

Once you have a GDT entry for this, you just put the byte offset of that entry into the CS register using a far jump. For example, make sure your GDT has room and the GDTR limit is increased to include the new entry, and then, say you put it as entry 6 (I think, it's been lunch since I looked at your code, that your TSS is entry 5), then the value you use as the segment for the far jump would be 0x30.

EDIT: You seem to be using the name "CS" for both the descriptor in the GDT and the actual CPU register CS. They are not the same thing. The GDT is just a table of data in RAM, and the segment registers point to it (in Protected Mode). A GDT entry is always 8 bytes long, so the value placed into a segment register (the selector) is always a multiple of 8. The lowest 3 bits of a selector are zero for a ring 0 selector that points to a GDT entry. A better way to label GDT entries in your code comments is to call them "ring 0 code" or "ring 3 data", because any segment register can be given any random value (doesn't mean the value is correct). Just happens that CS is the code segment and needs a selector that points to a code descriptor. The code descriptor is what has the attributes. Feel free ignore all this if it's too much or too repetitive, I just had a hard time following your previous post as it did not appear to use unique terms for each type of item present.

EDIT2: To actually answer your question: All CS does is POINT to a descriptor in the GDT, when you load CS the processor puts and verifies the GDT entry into parts of the register that are not accessible by code (called "hidden" for obvious reasons). Bits 16 - 3 of CS are the byte offset of the GDT entry to use, bit 2 if set means the entry actually comes from the LDT, bits 1-0 are the requested priviledge level. For stuff that all happens in the kernel, you don't need to set any of bits 0-2.
User avatar
neon
Member
Member
Posts: 1567
Joined: Sun Feb 18, 2007 7:28 pm
Contact:

Re: Realmode Switch Being Nasty

Post by neon »

Here is an example with code. Assuming you have 32 bit pmode entries and a null descriptor in the GDT, we can add two more entries to the GDT for 16 bit code and data:

Code: Select all

; gdt code:				; code descriptor (16bit)
	dw 0FFFFh 			; limit low
	dw 0x0 				; base low
	db 0 				; base middle
	db 10011010b 		; access
	db 00001111b 		; granularity
	db 0 				; base high

; gdt data:				; data descriptor (16bit)
	dw 0FFFFh 			; limit low (Same as code)
	dw 0x0 				; base low
	db 0 				; base middle
	db 10010010b 		; access
	db 00001111b 		; granularity
	db 0				; base high
Do to the null descriptor being at offset 0, 32 bit code descriptor at offset 0x8, data desciptor at offset 0x10, with our two new additions our new 16 bit code descriptor is at offset 0x18 and our 16 bit data descriptor is at offset 0x20. (Offsets are relative to the beginning of the GDT.)

So to jump into 16 bit protected mode using our GDT, do a jmp 0x18:ProtectedMode16 and you are in :)
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
User avatar
Creature
Member
Member
Posts: 548
Joined: Sat Dec 27, 2008 2:34 pm
Location: Belgium

Re: Realmode Switch Being Nasty

Post by Creature »

Yes, that is what was missing! I was confused about the GDT and such, but as soon as I started reading your posts I figured out what you meant.

I've increased the GDT entry count to 8 (I had 6 previously) and added the entries first:

Code: Select all

SetGDTGate(6, 0, 0x0000FFFF, 0x9A, 0x0F);	/* 16-bit code segment. */				//Offset 0x30
SetGDTGate(7, 0, 0x0000FFFF, 0x92, 0x0F);	/* 16-bit data segment. */				//Offset 0x38
and then changed my Assembly code to use the segments:

Code: Select all

...

; Step 3
	jmp 0x30:ProtectedMode16

[BITS 16]

ProtectedMode16:
	; Step 4 - 5
	mov ax, 0x38
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax

...
I also removed the code that set ax to 0x10 and then set up the other registers because they were previously set in protected mode and resetting them would be useless.

The code works now and I understand what I'm doing, all that is left now is to switch back to protected mode.

I have one question left; when I switch back to protected mode, will I need to reload the protected mode GDT? Or can I simply jump to my 32-bit protected mode code using 0x08 as GDT offset and then reload the data segment (0x10)? I'm referring to this piece of code:

Code: Select all


	mov eax, CR0					; Grab CR0.
	or eax, 0x80000001				; Set bit 1 and bit 31 (the protected mode and the paging bit).
	mov CR0, eax					; Store the new CR0.
	
	jmp 0x08:ProtectedMode32		; Jump to 32-bit protected mode (the code segment at offset 0x08 can be found in Interrupts.cpp).

[BITS 32]

ProtectedMode32:
	mov ax, 0x10					; Use 0x10 as data segment (again, see Interrupts.cpp).
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax
	
	mov esp, [ProtectedModeESP]		; Restore our original stack pointer.
	
	mov eax, [ProtectedModeCR3]
	mov CR3, eax					; Restore the page directory.
	
	popa							; Restore our original general purpose registers.
	
	lidt [ProtectedModeIDT]			; Restore our original IDT.
	
	sti								; Enable interrupts again.
	ret								; Return, we're all done here!
Thanks for all your help :),
Creature
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
User avatar
neon
Member
Member
Posts: 1567
Joined: Sun Feb 18, 2007 7:28 pm
Contact:

Re: Realmode Switch Being Nasty

Post by neon »

In my io_services routine, I use sgdt and sidt to store the current IDT and GDT to reload them upon exit when returning from real mode into protected mode. I suppose if IDTR and GDTR never change, you should not need to reload them. I personally dont take any chances though.
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
User avatar
Creature
Member
Member
Posts: 548
Joined: Sat Dec 27, 2008 2:34 pm
Location: Belgium

Re: Realmode Switch Being Nasty

Post by Creature »

Ah okay, thanks for the help. I first figured that was why my code wasn't working, but that was because I loaded the page directory after enabling paging instead of the other way around.
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
User avatar
Creature
Member
Member
Posts: 548
Joined: Sat Dec 27, 2008 2:34 pm
Location: Belgium

Re: Realmode Switch Being Nasty

Post by Creature »

I'm afraid I have another problem. However, it's pretty small though (at least I think). Everything is working fine now, I've been able to query available VESA modes and such and the kernel continues to run after the real mode switch (and thus, INT 10h) returns. However, I noticed that once I'm done with my video querying and code, interrupts don't work anymore (no interrupt handlers are called). A trivial STI instruction did not solve the problem, sadly. This only happens if I perform the BIOS interrupt at least once, so I'm sure it is the switcher and if I completely reinitialize both the GDT and IDT, interrupt work again (however, sometimes I get a double or triple fault, as there's some code in there that shouldn't be performed twice).

This lead me to the conclusion that my real mode switcher apparently isn't restoring the GDT or IDT properly (I'm almost sure it's the IDT). I did everything as I'm supposed to be doing it (at least I think):

Code: Select all

	//Code to go to real mode (pretty much the same as the code I previously posted).

	int 10h							; Perform the BIOS interrupt.
	cli								; Disable interrupts again (we're going back to protected mode).
	
	mov [RegsAX], ax				; |
	mov [RegsBX], bx				; | > Store the return values of the interrupt (if any).
	mov [RegsCX], cx				; | >
	mov [RegsDX], dx				; |
	
	mov eax, [ProtectedModeCR3]
	mov CR3, eax					; Restore the page directory.
	
	mov eax, CR0					; Grab CR0.
	or eax, 0x80000001				; Set bit 1 and bit 31 (the protected mode and the paging bit).
	mov CR0, eax					; Store the new CR0.
	
	jmp 0x08:ProtectedMode32		; Jump to 32-bit protected mode (the code segment at offset 0x08 can be found in Interrupts.cpp).

; ========================================
; ProtectedMode32
;   - Function taking care of resetting our 32-bit protected mode environment.
; ========================================
[BITS 32]

ProtectedMode32:
	mov ax, 0x10					; Use 0x10 as data segment (again, see Interrupts.cpp).
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax
	
	mov esp, [ProtectedModeESP]		; Restore our original stack pointer.

	popa							; Restore our original general purpose registers.
	
	lgdt [ProtectedModeGDT]			; Restore our original GDT.
	lidt [ProtectedModeIDT]			; ... and our IDT.
	
	sti								; Enable interrupts again.
	ret								; Return, we're all done here!
Any idea's what might be trampling my IDT or causing this problem?

Creature
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Realmode Switch Being Nasty

Post by Brendan »

Hi,
Creature wrote:Any idea's what might be trampling my IDT or causing this problem?
The code looks right to me, and if you are messing up the IDT or GDT then you'd probably get an exception or triple fault.

My guess is that an IRQ occurs while you're in real mode that the BIOS isn't expecting, and the BIOS doesn't send the EOI, and the PIC refuses to send lower priority IRQs after that. This is likely if you've remapped the PIC so that IRQs don't conflict with exceptions (e.g. IRQs 0 to 15 mapped to interrupts 32 to 47). For an example, the PIT/IRQ0 might occur and trigger an interrupt 32, and the BIOS would think this interrupt is an unsupported software interrupt (and just set the carry flag and IRET), and because IRQ0 is the highest priority IRQ the PIC can't send any IRQs at all after that.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Post Reply