Page 2 of 2

Posted: Wed Jul 18, 2007 5:23 pm
by sancho1980
Yet another problem :-(

In the "kernel", I have defined my gdt as follows:

Code: Select all

gdt:
gdt_null:
	dd 0
	dd 0
;(1)
gdt_code:
	dw 0ffffh	;limit of the segment
cbase1:	dw 0		;base address of segment
cbase2:	db 0		;still belonging to base address of segment
	db 10011010b	;1 for "segment is present"
			;00 for "privilege 0"
			;1 for "data or code segment"
			;1 for "code segment"
			;0 for "unconforming"
			;1 for readable
			;0 (access flag set by cpu on 1st access)
	db 11001111b	;1 for page-granularity
			;1 for 32 bit code segment
			;0 (reserved bit)
			;0 (available to system programmers)
			;1111b for last bits of segment limit
	db 0		;last byte of base address
;(2)
gdt_data:
	dw 0fffh	;limit of the segment
dbase1:	dw 0		;base address of segment
dbase2:	db 0		;still belonging to base address of segment
	db 10010010b	;1 for "segment is present"
			;00 for "privilege 0"
			;1 for "data or code segment"
			;0 for "data segment"
			;0 for "expand downward"
			;1 for writable
			;0 (access flag set by cpu on 1st access)
	db 11001111b	;1 for page-granularity
			;1 for 32-bit stack pointer
			;0 (reserved bit)
			;0 (available to system programmers)
			;1111b for last bits of segment limit
	db 0		;last byte of base address
While I'm STILL in real mode, I copy the ds base address into the respective fields in gdt_data:

Code: Select all

    mov ax, ds
    movzx eax, ax
    shl eax, 4			;eax now contains linear address of real mode ds
    mov ebx, eax		;move the linear address
    mov [dbase1], bx		;of the real mode ds
    shr ebx, 16			;into the base fields of
    mov [dbase2], bl		;the 32-bit data segment in the gdt
I did the same for the code segment...After turning on pm, I am able to jump to protected mode like this:

Code: Select all

    jmp 08h:enter_32	;jump into 32-bit segment!
Where enter_32 simply looks like this:

Code: Select all

enter_32:
        jmp 018h:return_16 (where the 3rd descriptor in the gdt is a 16 bit pm segment
This works well. Alas, it all starts to hang again as soon as I do something so trivial as this:

Code: Select all

enter_32:
    mov [realdata], ds			;ds still contains RM data segment
    mov ax, 010h			;copy the 32-bit data segment selector
    mov [stacksegment_16], ss		;make a backup of the ss register
    mov ds, ax				;into ds
    mov ax, [realdata]			;undo what
    mov ds, ax				;you just did
    jmp 018h:return_16 (where the 3rd descriptor in the gdt is a 16 bit pm segment
Is there anything wrong with how Im setting up the data segment descriptor?

Posted: Wed Jul 18, 2007 8:08 pm
by frank
I don't see anything wrong with your data segment descriptor. The thing I see that is wrong is:

Code: Select all

mov ax, [realdata]         ;undo what
mov ds, ax            ;you just did 
You usually cannot move a real mode value into a segment register in protected mode. You will have to wait until you are back in real mode to do that. It will generally cause a fault otherwise.

Posted: Wed Jul 18, 2007 8:59 pm
by Brendan
Hi,
sancho1980 wrote:Yet another problem :-(
Protected mode segments are references to descriptors in the GDT (and never make sense to the CPU in real mode). Real mode segments are "base address >> 4" and never make sense to the CPU in real mode. For example, loading 0x07C0 into DS does one of 2 completely different things depending on what mode the CPU is in - in protected mode it'd try to load details from the 248th GDT descriptor into DS, while in real mode it'd load the base address 0x7C00 into DS. Now, let's look at your code:

Code: Select all

enter_32:
    mov [realdata], ds
    mov ax, 010h
    mov [stacksegment_16], ss
    mov ds, ax
    mov ax, [realdata]
    mov ds, ax			;Attempt to load real mode segment into DS while in protected mode!
    jmp 018h:return_16
Mixing real mode and protected mode can be confusing, but there's things you could do to make it much less confusing...

First, why not arrange things so that the base address of all segments is always zero? This would mean you won't need to care about segment base addresses in any CPU mode. For example, start your boot loader with:

Code: Select all

    org 0x7C00
    bits 16

    jmp 0x0000:start

start:
And start your kernel with:

Code: Select all

    org 0x7E00
    bits 16

start:
Now you can "hard code" segment base addresses in your GDT (leave all segment base addresses set to zero, like they are already).

Next, don't keep switching CPU modes. Instead, do everything you need to in real mode, then switch to 32-bit protected mode and stay there.

For example:

Code: Select all

    org 0x7E00
    bits 16

start:
    ;** Do ALL real mode stuff **

;Switch to protected mode

    cli
    mov eax,cr0
    or al,1
    mov eax,cr0
    jmp 0x08:.here

    bits 32

.here:
    mov ax,0x10
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax
    mov esp,TOP_OF_STACK

    jmp $
If for some strange reason you really must switch back to real mode, then write a wrapper routine like this:

Code: Select all

doRealModeRoutine:
    mov ax,0x20
    mov ds,ax      ;* see notes later *
    mov es,ax      ;* see notes later *
    mov fs,ax      ;* see notes later *
    mov gs,ax      ;* see notes later *
    mov ss,ax
    jmp 0x18:.here

    bits 16
.here1:

    mov eax,cr0
    and al,0xFE
    mov eax,cr0
    jmp 0x0000:.here2
.here2:

    mov ax,0
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax

    call di

    cli
    mov eax,cr0
    or al,1
    mov eax,cr0
    jmp 0x08:.here3

    bits 32

.here3:
    mov ax,0x10
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax
    ret
A few notes might be appropriate for this - I (deliberately) didn't do any comments in this code, because I want you to add the comments while you figure out what it's doing (so you learn how it works rather than just doing "cut & paste"). This code uses the same stack in real mode and protected mode (but there isn't any alignment problems with the protected mode stack), so you'd need to make sure your protected mode stack is below 0x000010000.

To use this routine you'd do something like:

Code: Select all

    mov di, myRealModeDesignFlaw
    call doRealModeRoutine
    jmp $

    bits 16
myRealModeDesignFlaw:
    ;Do stuff that should've been done earlier
    ret
    bits 32
Lastly, to make working in real mode easier you may want to get rid of the 64 KB segment limits. To do this, try something like:

Code: Select all

    cli
    mov eax,cr0
    mov ebx,eax
    or al,1
    mov eax,cr0
    jmp .here1
.here1:
    mov ax,0x10
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax

    mov cr0,ebx
    jmp .here2
.here2:
    mov ax,0
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    ret
After you do that you can access everything in real mode. For example, you could do:

Code: Select all

     mov word [dword 0x000B8000],0x1F00 | '!'
Instead of:

Code: Select all

    push es
    mov ax,0xB800
    mov es,ax
    mov word [es:0x0000],0x1F00 | '!'
    pop es
In this case, if you use the "doRealModeRoutine" you'd want to remove the lines marked as "* see notes later *", because these lines restore the normal 64 KB segment limits for real mode.



Cheers,

Brendan

Posted: Thu Jul 19, 2007 7:05 am
by sancho1980
Brendan wrote:Now, let's look at your code:

Code: Select all

enter_32:
    mov [realdata], ds
    mov ax, 010h
    mov [stacksegment_16], ss
    mov ds, ax
    mov ax, [realdata]
    mov ds, ax			;Attempt to load real mode segment into DS while in protected mode!
    jmp 018h:return_16
I didnt mean to use the RM segment registers in pm; it was only an attempt to preload them before going back into RM. So I understand I first have to go back into pm before I can load these registers...
Brendan wrote:Mixing real mode and protected mode can be confusing, but there's things you could do to make it much less confusing...

First, why not arrange things so that the base address of all segments is always zero? This would mean you won't need to care about segment base addresses in any CPU mode.
But isn't the whole point of having segment registers and this whole protection mechanism in PM to protect programs from one another and to protect the kernel from less-privileged code? Also, doesnt setting all registers to zero make relocation more difficult, if not impossible?
Brendan wrote:Next, don't keep switching CPU modes. Instead, do everything you need to in real mode, then switch to 32-bit protected mode and stay there.
Thats true, but this was only an attempt to write something that doesnt just hang on startup: You boot and get the scan and ascii codes of whatever key you hit. You press enter and you call the "kernel", which switches over to protected mode, but doesnt do anything useful, so it returns. This way I can see if I have done a mistake. Normally, when you look for starters' boot code on the net, the code just executed and ends up hanging, so whenever you add something to it it you can't really tell if everything is going as intended but by writing my code in a way that it goes back to where it came from (I hope) I can be sure I am doing everything cleanly..

Did you forget something here or is this on purpose:
Brendan wrote:If for some strange reason you really must switch back to real mode, then write a wrapper routine like this:

Code: Select all

doRealModeRoutine:
    mov ax,0x20
    mov ds,ax      ;* see notes later *
    mov es,ax      ;* see notes later *
    mov fs,ax      ;* see notes later *
    mov gs,ax      ;* see notes later *
    mov ss,ax
    jmp 0x18:.here

    bits 16
.here1:
    ;no "cli"?
    mov eax,cr0
    and al,0xFE
    mov eax,cr0
    ;no "sti"?
    jmp 0x0000:.here2
.here2:

    mov ax,0
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax

    call di

    cli
    mov eax,cr0
    or al,1
    mov eax,cr0
    ;"cli" but no "sti"??
    jmp 0x08:.here3

    bits 32

.here3:
    mov ax,0x10
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax
    ret
And whats the point in jmp'ing to $:
Brendan wrote:

Code: Select all

    mov di, myRealModeDesignFlaw
    call doRealModeRoutine
    jmp $ ;whats the point?
    bits 16
myRealModeDesignFlaw:
    ;Do stuff that should've been done earlier
    ret
    bits 32
And have you forgotten something here:
Brendan wrote: Lastly, to make working in real mode easier you may want to get rid of the 64 KB segment limits. To do this, try something like:

Code: Select all

    cli ; no "sti" to un-"cli"?
    mov eax,cr0 ;eax:=cr0
    mov ebx,eax ;ebx:=eax=cr0
    or al,1
    mov eax,cr0
    jmp .here1
.here1:
    mov ax,0x10
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax

    mov cr0,ebx ;cr0:=ebx=cr0 => whats the point?
    jmp .here2
.here2:
    mov ax,0
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    ret
After you do that you can access everything in real mode.
I've heard of it, unreal mode, isnt it? But I cant really follow the logic of this snippet. Am I wrong or did you forget something? No time to try out at work, will do when I come back...

Thanks,

Martin

Posted: Thu Jul 19, 2007 8:54 am
by Brendan
Hi,
sancho1980 wrote:
Brendan wrote:Mixing real mode and protected mode can be confusing, but there's things you could do to make it much less confusing...

First, why not arrange things so that the base address of all segments is always zero? This would mean you won't need to care about segment base addresses in any CPU mode.
But isn't the whole point of having segment registers and this whole protection mechanism in PM to protect programs from one another and to protect the kernel from less-privileged code? Also, doesnt setting all registers to zero make relocation more difficult, if not impossible?
Originally (when 8086 was designed) the idea of segment registers was to give access to more than 64 KB of memory while still using 16-bit registers - rather than implementing 20-bit (or wider) registers, they used a pair of 16-bit registers for addressing to keep CPU costs down.

Protection came later - the 80286 had "16-bit protected mode" which was crappy. Not long after Intel moved to 32-bit and added paging (which has it's own protection). Now everyone uses paging for protection (and an 80386 or later CPU is expected) and segmentation is rarely used for anything.

More recently, for some modern extensions (SYSCALL/SYSRET and long mode/64-bit) the CPU manufacturers (mainly AMD) decided that segmentation wasn't worth bothering with (as no-one was using it anyway), and designed the extensions so that they can't really be used with segmentation. This is mostly for performance - doing all the protection checks involved with segmentation is a waste of CPU time when no-one uses segmentation.

In any case, how you set things up at the start of boot may not necessarily have anything to do with how things are setup after boot - typically all your boot code is running at the highest privilege level unrestricted by any protection mechanism (until you start thinking about how the CPUs protection and memory management will be used after boot).
sancho1980 wrote:Did you forget something here or is this on purpose:
Brendan wrote:If for some strange reason you really must switch back to real mode, then write a wrapper routine like this:
I assume that all the protected mode code runs with interrupts disabled, and therefore it's not necessary to disable interrupts when disabling protected mode. I didn't enable interrupts after returning to real mode as this is a generic routine - the real mode routine being called (via. the "call di") can enable interrupts or leave them disabled (whichever happens to be appropriate for that code).
sancho1980 wrote:And whats the point in jmp'ing to $:
Jumping to $ is the same as ".die: jmp .die" - it sends the CPU into an infinite loop. I used it because there's no more code for the CPU to run after the "jmp $". Think of it as an "Oops, need to write more code here" reminder. ;)
sancho1980 wrote:And have you forgotten something here:
Brendan wrote:Lastly, to make working in real mode easier you may want to get rid of the 64 KB segment limits. To do this, try something like:
Hehe - yes. I forgot the STI near the end of this routine. :)

The "mov cr0, ebx" is important though. First it gets a copy of the old CR0 into EBX, then changes CR0, then does some stuff, then restores the old value of CR0.
sancho1980 wrote:I've heard of it, unreal mode, isnt it? But I cant really follow the logic of this snippet. Am I wrong or did you forget something? No time to try out at work, will do when I come back...
Yes - "unreal mode" is probably one of the most common names for it. The logic isn't too hard to figure out if you're familiar with how the CPU handles segment limits - it enables protected mode, loads new data segments to change the segment limits to 4 GB, then disables protected mode (leaving the data segment limits set to 4 GB). Because the segment limits aren't changed by loading a segment register in real mode, you end up with "unreal mode".


Cheers,

Brendan

Posted: Thu Jul 19, 2007 9:48 am
by sancho1980
Brendan wrote:The "mov cr0, ebx" is important though. First it gets a copy of the old CR0 into EBX, then changes CR0, then does some stuff, then restores the old value of CR0.
But where do you change the value of cr0?

Thanks

Martin

[edit]Ahh, I think I know whats the matter. You probably meant to code

Code: Select all

mov cr0,eax
instead of

Code: Select all

mov eax,cr0 

Posted: Thu Jul 19, 2007 11:49 pm
by Brendan
Hi,
sancho1980 wrote:[edit]Ahh, I think I know whats the matter. You probably meant to code

Code: Select all

mov cr0,eax
instead of

Code: Select all

mov eax,cr0 
Doh - yes (I didn't even see that bug :oops:).


Cheers,

Brendan