Page 1 of 1

Using [ORG 0] + real mode segments in bootloader.

Posted: Wed Mar 03, 2010 2:43 am
by mangaluve
I have a bootloader taken from a tutorial (http://www.osdever.net/tutorials/view/mixing-assembly-c), which works fine. It loads my kernel, switches to protected mode, and starts executing my C code. However, the code uses [ORG 0x7C00] in the beginning of the bootloader. I'd like to use real mode segments instead (to be able to relocate the bootloader). Thus, I use [ORG 0] and at the first line of the code I write something like

Code: Select all

jmp 07C0h:entry
entry:
This is the code with my modification

Code: Select all

[BITS 16]       ; We need 16-bit intructions for Real mode

[ORG 0x0]    ; The BIOS loads the boot sector into memory location 0x7C00

jmp 07C0h:reset_drive
reset_drive:
        mov ah, 0               ; RESET-command
        int 13h                 ; Call interrupt 13h
        or ah, ah               ; Check for error code
        jnz reset_drive         ; Try again if ah != 0

        mov ax, 0
        mov es, ax
        mov bx, 0x1000          ; Destination address = 0000:1000

        mov ah, 02h             ; READ SECTOR-command
        mov al, 02h             ; Number of sectors to read = 1
        mov ch, 0               ; Cylinder = 0
        mov cl, 02h             ; Sector = 2
        mov dh, 0               ; Head = 0
        int 13h                 ; Call interrupt 13h
        or ah, ah               ; Check for error code
        jnz reset_drive         ; Try again if ah != 0

        cli                     ; Disable interrupts, we want to be alone

        xor ax, ax
        mov ds, ax              ; Set DS-register to 0 - used by lgdt

        lgdt [gdt_desc]         ; Load the GDT descriptor

        mov eax, cr0            ; Copy the contents of CR0 into EAX
        or eax, 1               ; Set bit 0
        mov cr0, eax            ; Copy the contents of EAX into CR0

        jmp 08h:clear_pipe      ; Jump to code segment, offset clear_pipe


[BITS 32]                       ; We now need 32-bit instructions
clear_pipe:
        mov ax, 10h             ; Save data segment identifyer
        mov ds, ax              ; Move a valid data segment into the data segment register
        mov ss, ax              ; Move a valid data segment into the stack segment register
        mov esp, 090000h        ; Move the stack pointer to 090000h

        jmp 08h:01000h          ; Jump to section 08h (code), offset 01000h


gdt:                    ; Address for the GDT

gdt_null:               ; Null Segment
        dd 0
        dd 0

gdt_code:               ; Code segment, read/execute, nonconforming
        dw 0FFFFh
        dw 0
        db 0
        db 10011010b
        db 11001111b
        db 0

gdt_data:               ; Data segment, read/write, expand down
        dw 0FFFFh
        dw 0
        db 0
        db 10010010b
        db 11001111b
        db 0

gdt_end:                ; Used to calculate the size of the GDT



gdt_desc:                       ; The GDT descriptor
        dw gdt_end - gdt - 1    ; Limit (size)
        dd gdt                  ; Address of the GDT




times 510-($-$$) db 0           ; Fill up the file with zeros

        dw 0AA55h                ; Boot sector identifyer
This doesnt work, the computer reboots. However, if I change [ORG 0] to [ORG 0x7C00], and remove the first far jump, it works. What could this be?

Re: Using [ORG 0] + real mode segments in bootloader.

Posted: Wed Mar 03, 2010 2:56 am
by AJ
Hi,

The problem is that you are using flat protected mode segments at offset 0. You have to use a far jump to finish entering protected mode, and that uses an absolute address. Your assembler encodes this address as 0+clear_pipe, when the actual instruction should be 0x7C00+clear_pipe.

You have a few of ways to deal with this:
* Run your code from 0x0000:0x7C00 (by far the easiest).
* Encode the far jump as 0x08:(clear_pipe + 0x7C00)
* Emit the far jump instruction manually and jump to the location of your manually emitted instruction.
* Use a GDT with segment offsets of 0x7C00.
* Load the PMode section of your code in a second disk sector, with the code linked at a known offset.

Bear in mind that if you use the second or third options, you will have to add an offset to all label references made in the PMode section of your code.

Cheers,
Adam

Re: Using [ORG 0] + real mode segments in bootloader.

Posted: Wed Mar 03, 2010 3:15 am
by mangaluve
Thanks a lot!

I tried your second suggestion, entering protected mode by the far jump

Code: Select all

jmp 08h:(clear_pipe + 0x7C00)
but that doesn't seem to work either. I don't really use any other labels, the only thing I'll do after switching to PM is to jump to my C kernel.

The problem is that I will change my loader so it relocates itself, why I cant use ORG.

Re: Using [ORG 0] + real mode segments in bootloader.

Posted: Wed Mar 03, 2010 3:35 am
by qw
The same counts for gdt_desc.

Keep it simple. Either use [ORG 7C00H] and load all segment registers with 0 or use [ORG 0] and load all segment registers with 07C0H.

BTW you'd better set up a stack at the beginning of the real mode code too.

Re: Using [ORG 0] + real mode segments in bootloader.

Posted: Wed Mar 03, 2010 3:39 am
by mangaluve
I also changed it to

Code: Select all

lgdt [gdt_desc + 0x7C00]
but it still reboots. Where is the stack pointer pointing "by default" when the computer starts? Do I use any instructions that uses/affects the stack in real mode?

Nevermind, I found it :) Thanks for all the help. But now Im starting to become aware of the fact that I probably need to think through where to put everything in the memory. Where do you usually put the stack in PM? At the end of the memory?

Re: Using [ORG 0] + real mode segments in bootloader.

Posted: Wed Mar 03, 2010 3:43 am
by qw
The second field of gdt_desc must be the linaer address of the GDT, so you should add 7C00H there too.

Really, it's much easier to use [ORG 7C00H].

There is no default stack. You should set up a stack yourself.

Re: Using [ORG 0] + real mode segments in bootloader.

Posted: Wed Mar 03, 2010 3:47 am
by mangaluve
Thanks!

As you say, using ORG 0x7C00 is easier, but that cant be done if I want to relocate the boot loader, right?

Re: Using [ORG 0] + real mode segments in bootloader.

Posted: Wed Mar 03, 2010 6:23 am
by qw
Yes it can. If you relocate the boot loader to 0000:0600H for example, then use [ORG 0600H] but do not reference any symbol until the relocation is complete.

Re: Using [ORG 0] + real mode segments in bootloader.

Posted: Wed Mar 03, 2010 7:36 am
by mangaluve
Im using NASM, and two ORG directives don't seem to be allowed...

It's strange however, cause I got the code working, by adding 0x7C00 to all labels that are used in PM. However, now I tried to relocate the boot loader. Everything works fine, if I keep adding 0x7C00 to the labels (since the "old" boot loader is still there, it hasn't been overwritten). However, when I instead try to add the address of the new location, it reboots.

The following code works

Code: Select all

;
; boot.asm
;

[BITS 16]
[ORG 0x0]

%define BOOTSECTOR_SIZE 	512				; The size of our boot sector (duh!)

%define BOOT_SEG 			07C0h			; Where BIOS loads our loader
%define INIT_SEG 			09000h			; Where the loader should relocate itself

jmp BOOT_SEG:boot_entry					; Fill CS with the code segment 0x7C00

; The following piece of code will relocate the loader to 0x90000 and continue the execution there.
boot_entry:
	cli                     	; Disable interrupts, we want to be alone
	mov ax, cs					; The far-jump above makes CS point to out boot segment
	mov ds, ax
	mov ax, INIT_SEG
	mov es, ax
	xor di, di
	xor si, si
	mov cx, BOOTSECTOR_SIZE
	cld							; Clear the flags
	rep movsb					; Move the boot loader to 0x90000

	jmp INIT_SEG:boot_postmove 	; Jump into newly written code

; This is where execution will continue. The boot loader is now located at 0x90000, which will be the
; content of the CS register because of the far jump above.
boot_postmove:
	mov ax, cs 					; Set all segments to code segment
	mov ds, ax
	mov ss, ax
	mov es, ax
	mov fs, ax
	mov gs, ax

	mov sp, INIT_SEG + BOOTSECTOR_SIZE		; So we dont overwrite the boot sector with the stack

    cli                     ; Disable interrupts, we want to be alone

    xor ax, ax
    mov ds, ax              ; Set DS-register to 0 - used by lgdt

    lgdt [gdt_desc + 16 * BOOT_SEG]         ; Load the GDT descriptor

    mov eax, cr0            ; Copy the contents of CR0 into EAX
    or eax, 1               ; Set bit 0
    mov cr0, eax            ; Copy the contents of EAX into CR0

    jmp 08h:(clear_pipe + 16 * BOOT_SEG)      ; Jump to code segment, offset clear_pipe

[BITS 32]                       ; We now need 32-bit instructions
clear_pipe:
	jmp clear_pipe

gdt:                    ; Address for the GDT

gdt_null:               ; Null Segment
        dd 0
        dd 0

gdt_code:               ; Code segment, read/execute, nonconforming
        dw 0FFFFh
        dw 0
        db 0
        db 10011010b
        db 11001111b
        db 0

gdt_data:               ; Data segment, read/write, expand down
        dw 0FFFFh
        dw 0
        db 0
        db 10010010b
        db 11001111b
        db 0

gdt_end:                ; Used to calculate the size of the GDT

gdt_desc:
        dw gdt_end - gdt - 1
        dd gdt + 16 * BOOT_SEG

times BOOTSECTOR_SIZE-2-($-$$) db 0           ; Fill up the file with zeros
        dw 0AA55h                			  ; Boot sector identifyer
However, if I replace the line

Code: Select all

jmp 08h:(clear_pipe + 16 * BOOT_SEG)
with

Code: Select all

jmp 08h:(clear_pipe + 16 * INIT_SEG)
the system reboots. But the code SHOULD be located at INIT_SEG, since the jump

Code: Select all

jmp INIT_SEG:boot_postmove
after relocation, is successful.

Even more weird, this code runs

Code: Select all

jmp   08h:(clear_pipe + 16 * BOOT_SEG)  

[BITS 32]
clear_pipe:
	jmp   08h:(clear_pipe + 16 * INIT_SEG)
but the following

Code: Select all

jmp   08h:(clear_pipe + 16 * INIT_SEG)  

[BITS 32]
clear_pipe:
	jmp   08h:(clear_pipe + 16 * INIT_SEG)
fails.

Re: Using [ORG 0] + real mode segments in bootloader.

Posted: Wed Mar 03, 2010 8:08 am
by mangaluve
It works now with

Code: Select all

jmp dword 08h:(clear_pipe + 16 * INIT_SEG)
I suppose the INIT_SEG address was too long...