Can't make the jump... to protected

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
User avatar
Code0x58
Posts: 10
Joined: Tue Jul 27, 2010 2:13 pm
Location: Surrey, England

Can't make the jump... to protected

Post by Code0x58 »

Hi, I've been trying to figure this out myself to no avail for a while now so have come to seek help with getting the problem sorted. I've been trying to make a 32-bit protected minuscule OS with a mix of 16-bit NASM for the entry point and then a GCC C kernel with 32-bit NASM for a tight rein over certain functions (mainly for being pedantic than speed requirements really), the 16-bit code is just a 512B boot-sector that is meant to load the rest of the drive into memory, enable 32-bit protected mode and then jump past the end of the code where a stub of 32-bit NASM code is to call the C kernel and on return just halt. The 32-bit NASM and C are linked with Open Watcom's wlink as it can link to a flat file which I want so I can avoid GRUB and ELFs for now and be slightly more hands on.

The problem is when I try to do a long jump from the 16-bit boot code to 0x08:0x7e00 which is where the 32-bit code is it just triple faults, I can load the data segment selectors without failing, the GDT appears to have been loaded with the proper address and the addressing everywhere seems fine to me. I guessed that my GDT entry was wrong but it seems all right with what I've checked it against. So far I've narrowed down the crash to being caused by loading CS with a far jump after the Protected mode bit has been set in CR0* but for the life of me haven't been able to work out why even with disassembling the compiled code and checking the code of other projects. I am testing it on VMWare Workstation 7.0.0. I think that's just about all I can say about it and so here is my source code:
boot.asm:

Code: Select all

;    This code should produce 16-bit x86 code that will be loaded into
;    memory at 0x7c00 by the BIOS as it has the boot sector marker
;    (0xaa55) at the end. The code is limited to 512B in size and it's
;    purpose is to load the rest of the OS into memory from a floppy
;    drive, load up a flat GDT and then jump past the end of the code
;    where 32-bit code will be for the rest of the OS (32-bit mode is
;    activated by the far jump).

%include "include/macros.mac"
[BITS 16]
[ORG 0x7c00]
; clear all segment registers so addressing is correct for all code under
; 64KiB
xor    ax,ax
mov    ds,ax
mov    es,ax
mov    fs,ax
mov    gs,ax
mov    ss,ax
jmp    word 0:Boot
Boot:

; load the next 65 sectors (32.5KiB), should load 1439.5KiB (rest of disk)
; wang disks consist of sectors of 512B, 18 per tracks, 80 tracks per side
; and two sides leading to 1440KiB, 1.44MB my @$$, the liars >:( CF will
; be set if operation FAILED
mov    ax,0x0241
mov    bx,0x7e00
mov    cx,0x0001
mov    dh,0
int    0x13
jc    Terminal

;; enable A20 address line using BIOS, CF will be set if failed
mov    ax,0x2401    
int    0x15
jc    Terminal

; disable interrupts as there is no IVT/IDT otherwise when entering 32-bit
; mode the world with abruptly turn to faecal matter
cli
; Load GDT into GDTIR and enable protected mode bit of CR0
lgdt    [GDT]

mov    eax,cr0
or    eax,byte 1
mov    cr0,eax

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

mov    ebp,32 MiB
mov    esp,ebp

; Lunge forth into protected mode at 0x7e00 (just after boot block), sets
; CS to 0x08 where the CODE GDT entry is.
jmp    word CODE:END

; Resort to this in the event of an error which we CBA to account for
; this will triple fault as soon as protected mode is activated as it uses
; segment registers (es)
Terminal:
mov    ax,0xb800
mov    es,ax
mov    si,fatal_message;.msg

xor    bx,bx
mov    cx,fatal_message._len
.put:
    mov    ax,word[si]
    mov    word[es:bx],ax
    add    si,2
    add    bx,2
    dec    cx
jnz    .put
cli
hlt

fatal_message:
VGASTRING 0x09,"A case that has "
VGASTRING 0x04,"NOT "
VGASTRING 0x09,"been accounted for has occured, Goodbye!"
FINALIZE fatal_message,VGA

;==============================================================================
;===Global Descriptor Table ===================================================
;==============================================================================
align(4)
; use the first entry in the GDT to store the GDT information to be used with
; lgdt as it's never used, right?
GDT:
    dw    GDT.end - GDT - 1
    dd    GDT
    dw    0
; entry 1, addr 0x08
CODE    EQU    .flat_code - GDT
.flat_code db 0xff,0xff,0x00,0x00,0x00,0x9a,0xcf,0x00
; entry 1, addr 0x10
DATA    EQU    .flat_data - GDT
.flat_data db 0xff,0xff,0x00,0x00,0x00,0x92,0xcf,0x00 
    
.end:
;==============================================================================
times 510-($-$$) db 0 ; if this fails the code is too long
db 0x55
db 0xaa
; end of code, will be at 0x7e00
END:
include/macros.mac:

Code: Select all

%ifndef MACROS_MAC
%define MACROS_MAC	1

; align data/code to boundry
%define align(n) times ((n - (($-$$) % n)) % n) nop

; size macros
%define KiB * (1<<10)
%define MiB * (1<<20)

; define an external C label
%macro EXTERNALC 1
	extern _%1
	%1 EQU _%1
%endmacro
; export an internal label for C
%macro EXPORTC 1
	%define %1 _%1
	global _%1
	%1:
%endmacro

; define a VGA string with %1 as character attributes, the string is defined by %2 and it's length is in label.len
%macro VGASTRING 2
%assign a %1
%assign i 1
%strlen len %2
%rep len
	%substr c %2 i
	%assign i i+1
	dw (c + (a << 8))
%endrep
%endmacro

%assign VGA 2

;gets the number of bytes from %1 to $ and sets %1._len to that value
%macro FINALIZE 1-2 1
%1._len EQU (($ - %1) / %2)
%endmacro

%endif
The jmp word CODE:END (jmp 0x08:0x7e00, 0xEA_007E_0800) on line 61 in boot.asm is where the problem shows up. I have omitted the C+32-bit NASM and build script as from what I can tell they aren't involved at this point. If anyone could shed some light on this it would be greatly appreciated!

*as a side note the article on the tutorials at osdever.net (recommended on the IRC channel) says that enabling protected mode is done by CR3 = CR0 | 1 which I think is wrong, wouldn't that only be if you were using pages or something along those lines?
User avatar
bewing
Member
Member
Posts: 1401
Joined: Wed Feb 07, 2007 1:45 pm
Location: Eugene, OR, US

Re: Can't make the jump... to protected

Post by bewing »

To me, it looks like the problem is:

when you store eax back into CR0 with the pmode bit set, there is a one instruction delay and then the CPU goes into 32 bit mode. But you didn't tell NASM that the CPU changed to 32 bit mode, so NASM is still producing 16 bit code at that point. You have 8 instructions in a row that are using the wrong BITS size.

The easiest way to handle a pmode switch is to have the far JMP be immediately after the MOV CR0, EAX instruction. Then set the data segment registers using 32bit code AFTER the far jump completes.
If this means you need to do a far jump within your own bootsector, then set the segment registers, and then a near jump to your OS, then that's what you need to do.

And you are correct, and osdever is wrong. Don't touch CR3 until you are doing virtual memory paging.
User avatar
Code0x58
Posts: 10
Joined: Tue Jul 27, 2010 2:13 pm
Location: Surrey, England

Re: Can't make the jump... to protected

Post by Code0x58 »

Thanks for the reply, I've tried before, and right now for certainty, with the long jump straight after updating CR0 as well as telling NASM to generate 32-bit code at that point both with no change. I think that the processor doesn't switch to 32-bit mode until the segment selector is changed as it retains the segment descriptor from prior to the CR0 update, or at least that is the impression I am under at the moment.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Can't make the jump... to protected

Post by Brendan »

Hi,

It seems mostly right to me.

When you use the BIOS disk services you're meant to retry up to 3 times, because sometimes there's temporary failures (e.g. floppy disk motor not up to speed on the first try, or read errors where you might be lucky next time). Also, you shouldn't set SS (then allow a possible IRQs to occur with mismatched SS:SP) then change SP later - you need to either disable interrupts to prevent an IRQ handler using a dodgy stack, or use the LSS instruction to set SS and SP at the same time, or put the "mov esp" immediately after the "mov ss" (because there's a special hack in modern CPUs to disable IRQs for the instruction after a "mov ss").

Of course none of these things are causing your problem.

The best way to debug something like this is to single-step with something like the debugger built into Bochs. This would allow you to check exactly where it crashes, examine memory contents (to see if the second sector was correctly loaded at 0x0007E000), look at the GDT, etc.


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.
gerryg400
Member
Member
Posts: 1801
Joined: Thu Mar 25, 2010 11:26 pm
Location: Melbourne, Australia

Re: Can't make the jump... to protected

Post by gerryg400 »

Try jmp ing to somewhere in the bootsector and then loop there. That way, even if your loader doesn't work, you will have a valid destination.
If a trainstation is where trains stop, what is a workstation ?
User avatar
Code0x58
Posts: 10
Joined: Tue Jul 27, 2010 2:13 pm
Location: Surrey, England

Re: Can't make the jump... to protected

Post by Code0x58 »

Thanks for the pointers and suggesting bochs Brendan, it's debugger has been invaluable - it turns out I used the wrong sector number in the floppy read so read the first sector into 0x7e00 instead of the second and so on! I feel a bit of a fool now but that's not surprising.

@gerryg400, I could have sworn I did some far jumping to 32-bit code within the first sector but I guess that wasn't the case given the fault was a bad attempt at loading the next sector, so that too was a good suggestion.

Thank you all for your contributions, they have provided a great relief to my situation and have given me a good example of the forum community :D
gerryg400
Member
Member
Posts: 1801
Joined: Thu Mar 25, 2010 11:26 pm
Location: Melbourne, Australia

Re: Can't make the jump... to protected

Post by gerryg400 »

I think that the processor doesn't switch to 32-bit mode until the segment selector is changed as it retains the segment descriptor from prior to the CR0 update, or at least that is the impression I am under at the moment.
Yep, that's right, you'll still be in 16bit mode until you far jmp and the far jmp will typically need an 0x66 operand size override to jmp to a 16:32bit address. But (the books say) you must perform a serialising instruction immediately after you switch to protected mode to clear the pre-fetch queue. The thing is though, mov to cr0 IS a serialising instruction on newer cpus. So perhaps it's no longer necessary.

The Intel book says
3.Execute a MOV CR0 instruction that sets the PE flag (and optionally the PG flag) in control register CR0.
4.Immediately following the MOV CR0 instruction, execute a far JMP or far CALL instruction. (This operation is typically a far jump or call to the next instruction in the instruction stream.)....

and further down

Random failures can occur if other instructions exist between steps 3 and 4 above. Failures will be readily seen in some situations, such as when instructions that reference memory are inserted between steps 3 and 4 while in system management mode.
My advice is do it any way.
If a trainstation is where trains stop, what is a workstation ?
Nugget
Posts: 16
Joined: Sun May 02, 2010 2:54 am

Re: Can't make the jump... to protected

Post by Nugget »

Hi,

You might also want to check the number of sectors you are reading. You can't read more than one tracks worth at a time. More that 18 sectors was intermittently successful in Bochs when I was trying it.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: Can't make the jump... to protected

Post by Combuster »

when you store eax back into CR0 with the pmode bit set, there is a one instruction delay and then the CPU goes into 32 bit mode.
Correction, after setting PE in CR0, you are in 16-bit protected mode.

(invalid point removed)
Last edited by Combuster on Thu Jul 29, 2010 1:02 am, edited 1 time in total.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
Dario
Member
Member
Posts: 117
Joined: Sun Aug 31, 2008 12:39 pm

Re: Can't make the jump... to protected

Post by Dario »

I taught that first descriptor >>>must<<< be zeroed out. Apparently, I was wrong.
____
Dario
Ready4Dis
Member
Member
Posts: 571
Joined: Sat Nov 18, 2006 9:11 am

Re: Can't make the jump... to protected

Post by Ready4Dis »

Dario wrote:I taught that first descriptor >>>must<<< be zeroed out. Apparently, I was wrong.
No, it doesnt' have to be zeroed out, using it is perfectly legit, I've been using that trick for a while now.
User avatar
Code0x58
Posts: 10
Joined: Tue Jul 27, 2010 2:13 pm
Location: Surrey, England

Re: Can't make the jump... to protected

Post by Code0x58 »

I've now changed to having the far jump directly after changing CR0 as per your advice gerryg400 although I'm still using a 16-bit address as I'm no going beyond the first 64KiB, when you say typically need the 0x66 before making the jump, I assume that's only for jumping above 64KiB or if you feel inclined to use a 32-bit destination and not that a lot of processors expect you to do so?

The only reason I used that large number of sectors (0x41) was for some reason I could get away with it on VMWare before it would flag an error, I did find that with this number of sectors it caused bochs to spin off somewhere and appeared as though the VM managed to get stuck appearing to always be returning from a recursive call as whenever I stepped through execution:

Code: Select all

[0x000f4040] f000:4040 (unk. ctxt): mov sp, bp                ; 89ec
[0x000f4042] f000:4042 (unk. ctxt): pop bp                    ; 5d
[0x000f4043] f000:4043 (unk. ctxt): ret                       ; c3
I haven't looked into it any further as I just changed it to a single sector, but now I'll sort out the loading code to load the rest of the 1440KiB from the drive.
gerryg400
Member
Member
Posts: 1801
Joined: Thu Mar 25, 2010 11:26 pm
Location: Melbourne, Australia

Re: Can't make the jump... to protected

Post by gerryg400 »

...when you say typically need the 0x66 before making the jump, I assume that's only for jumping above 64KiB or if you feel inclined to use a 32-bit destination...
Yes, if jmping to the 1MB mark for example you need an operand over-ride.

I just noticed that in my Intel book, in the mode switching example, they do a near jump after entering protected mode. They remain in 16 bit mode until doing an iret later. So my guess is that mov to cr0 serialises the processor but somehow a jmp is still required to flush the prefetch queue.

And it seems that any jmp would do that. Near or far. So Intel recommends a far jmp, but in the example show a near jmp.
If a trainstation is where trains stop, what is a workstation ?
Post Reply