Page 1 of 1

Error Loading 64-bit Code From My Own Custom Boot Loader

Posted: Sat Nov 02, 2013 2:21 pm
by kemosparc
Hi,

I am trying to boot 64-bit code from my own custom boot loader.

I have been working on that for the past 10 days, and I almost read and triedout all the tutorials and the work presented on OSDev related to this.

My code is divided into two ASM files:

1.Custom Boot Loader:

Code: Select all

; Fat12 Bootloader

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

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

jmp word load_kernel            ; Load the OS Kernel

drive db 0                      ; Used to store boot device
        

;----------Bootsector Code----------;
halt:
 jmp halt

;----------Bios Print Rootine----------;
bios_print:
	lodsb
	or al, al  ;zero=end of str
	jz done    ;get out
	mov ah, 0x0E
	int 0x10
	jmp bios_print
	done:
ret


;----------Sector Load Error Handler----------;
cannot_read_sector:
	mov si, cannot_read_sector_msg
	call bios_print
	jmp halt

;----------Check A20 Line Address----------;
EnableA20:
	pushf
	push ds
	push es
	push di
	push si
	mov ax, 0x2401
	int 0x15
	pop si
	pop di
	pop es
	pop ds
	popf 
;ret

;----------Second Stage Boot Loader----------;
SecondStage:
	mov ah, 0               ; RESET-command
	int 13h                 ; Call interrupt 13h
	mov [drive], dl         ; Store boot disk
	or ah, ah               ; Check for error code
	jnz cannot_read_sector  ; Try again if ah != 0
	mov ax, 0x0
	mov es, ax
	mov bx, 0x1000       ; Destination address = 0000:1000

	mov ah, 02h             ; READ SECTOR-command
	mov al, 12h             ; Number of sectors to read (0x12 = 18 sectors)
	mov dl, [drive]         ; Load boot disk
	mov ch, 0               ; Cylinder = 0
	mov cl, 2               ; Starting Sector = 3
	mov dh, 0               ; Head = 1
	int 13h                 ; Call interrupt 13h

	or ah, ah               ; Check for error code
	jnz cannot_read_sector
ret

;----------From Real Mode ----------;
FromReal:

	cli                     ; Disable interrupts, we want to be alone
	push ds
	xor ax, ax              ; Clear AX register
	mov ds, ax              ; Set DS-register to 0 - used by lgdt
	lgdt [gdt_desc]         ; Load the GDT descriptor 

	mov eax, cr0            ; Set the A-register to control register 0.
	or eax, 1               ; Set the PG-bit, which is the 31nd bit, and the PM-bit, which is the 0th bit.
	mov cr0, eax            ; Set control register 0 to the A-register.
	pop ds
 
ret

;----------Start Loading The Kernel----------;
load_kernel:
	mov si, welcome_msg
	call bios_print
	call SecondStage
	call EnableA20
	call FromReal


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

[BITS 32]                       ; We now need 32-bit instructions

kernel_segments:

        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:0x1000

halt1:
	jmp halt1

;----------Global Descriptor Table----------;

gdt:                            ; Address for the GDT

gdt_null:                       ; Null Segment
        dd 0
        dd 0


KERNEL_CODE             equ $-gdt
gdt_kernel_code:
        dw 0FFFFh               ; Limit 0xFFFF
        dw 0                    ; Base 0:15
        db 0                    ; Base 16:23
        db 09Ah                 ; Present, Ring 0, Code, Non-conforming, Readable
        db 0CFh                 ; Page-granular
        db 0                    ; Base 24:31

KERNEL_DATA             equ $-gdt
gdt_kernel_data:
        dw 0FFFFh               ; Limit 0xFFFF
        dw 0                    ; Base 0:15
        db 0                    ; Base 16:23
        db 092h                 ; Present, Ring 0, Data, Expand-up, Writable
        db 0CFh                 ; Page-granular
        db 0                    ; Base 24:32

gdt_interrupts:
        dw 0FFFFh
        dw 01000h
        db 0
        db 10011110b
        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




welcome_msg   db 'Welcome to new OS', 13, 10, 0
cpu_not_supported   db 'CPU is not supported', 13, 10, 0
cannot_read_sector_msg   db 'Cannot load second stage', 13, 10, 0

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

dw 0AA55h                       ; Boot sector identifyer
2.Entry point to the kernel:

Code: Select all

[BITS 32]
jmp Main32

GDT64:                           ; Global Descriptor Table (64-bit).
        .Null: equ $ - GDT64         ; The null descriptor.
        dw 0                         ; Limit (low).
        dw 0                         ; Base (low).
        db 0                         ; Base (middle)
        db 0                         ; Access.
        db 0                         ; Granularity.
        db 0                         ; Base (high).
        .Code: equ $ - GDT64         ; The code descriptor.
        dw 0                         ; Limit (low).
        dw 0                         ; Base (low).
        db 0                         ; Base (middle)
        db 10011000b                 ; Access.
        db 00100000b                 ; Granularity.
        db 0                         ; Base (high).
        .Data: equ $ - GDT64         ; The data descriptor.
        dw 0                         ; Limit (low).
        dw 0                         ; Base (low).
        db 0                         ; Base (middle)
        db 10010000b                 ; Access.
        db 00000000b                 ; Granularity.
        db 0                         ; Base (high).
        .Interrupt: equ $ - GDT64    ; The interrupt descriptor.
        dw 0                         ; Limit (low).
        dw 0                         ; Base (low).
        db 0                         ; Base (middle)
        db 10011110b                 ; Access.
        db 11001111b                 ; Granularity.
        db 0

    .Pointer:                    ; The GDT-pointer.
    dw $ - GDT64 - 1             ; Limit.
    dq GDT64                     ; Base.


cpu_not_supported   db 'E1', 13, 10, 0
no_long_mode_msg   db 'E3', 13, 10, 0




halt1:
	jmp halt1
;----------No CPU Handler----------;
NoCPUID:
;	mov si, cpu_not_supported
;	call bios_print
	jmp halt1

;----------No Long Mode Handler----------;
NoLongMode:
;        mov si, no_long_mode_msg
;        call bios_print
        jmp halt1

;----------Check CPU Supports 64-bit Routine----------;
CheckCPU:
	pushfd               ; Store the FLAGS-register.
	pop eax              ; Restore the A-register.
	mov ecx, eax         ; Set the C-register to the A-register.
	xor eax, 1 << 21     ; Flip the ID-bit, which is bit 21.
	push eax             ; Store the A-register.
	popfd                ; Restore the FLAGS-register.
	pushfd               ; Store the FLAGS-register.
	pop eax              ; Restore the A-register.
	push ecx             ; Store the C-register.
	popfd                ; Restore the FLAGS-register.
	xor eax, ecx         ; Do a XOR-operation on the A-register and the C-register.
	jz NoCPUID          ; The zero flag is set, no CPUID.
ret

;----------Detect Long Mode----------;
DetectLong:
        mov eax, 0x80000000    ; Set the A-register to 0x80000000.
        cpuid                  ; CPU identification.
        cmp eax, 0x80000001    ; Compare the A-register with 0x80000001.
        jb NoLongMode         ; It is less, there is no long mode.

        mov eax, 0x80000001    ; Set the A-register to 0x80000001.
        cpuid                  ; CPU identification.
        test edx, 1 << 29      ; Test if the LM-bit, which is bit 29, is set in the D-register.
        jz NoLongMode         ; They aren't, there is no long mode.
ret
;----------Enable Paging----------;
EnablePaging:
        pushf
        push ds
        push es
        push di
        push si

        mov eax, cr0                                   ; Set the A-register to control register 0.
        and eax, 01111111111111111111111111111111b     ; Clear the PG-bit, which is bit 31.
        mov cr0, eax                                   ; Set control register 0 to the A-register.

        mov edi, 0x3000    ; Set the destination index to 0x3000.
        mov cr3, edi       ; Set control register 3 to the destination index.
        xor eax, eax       ; Nullify the A-register.
        mov ecx, 4096      ; Set the C-register to 4096.
        rep stosd          ; Clear the memory.
        mov edi, cr3       ; Set the destination index to control register 3.

        mov DWORD [edi], 0x4003      ; Set the double word at the destination index to 0x2003.
        add edi, 0x1000              ; Add 0x3000 to the destination index.
        mov DWORD [edi], 0x5003      ; Set the double word at the destination index to 0x3003.
        add edi, 0x1000              ; Add 0x3000 to the destination index.
        mov DWORD [edi], 0x6003      ; Set the double word at the destination index to 0x4003.
        add edi, 0x1000              ; Add 0x3000 to the destination index.

        mov ebx, 0x00000003          ; Set the B-register to 0x00000003.
            mov ecx, 512                 ; Set the C-register to 512.

        .SetEntry:
            mov DWORD [edi], ebx         ; Set the double word at the destination index to the B-register.
            add ebx, 0x1000              ; Add 0x3000 to the B-register.
            add edi, 8                   ; Add eight to the destination index.
            loop .SetEntry               ; Set the next entry.

        mov eax, cr4                 ; Set the A-register to control register 4.
        or eax, 1 << 5               ; Set the PAE-bit, which is the 6th bit (bit 5).
        mov cr4, eax                 ; Set control register 4 to the A-register.
        pop si
        pop di
        pop es
        pop ds
        popf
ret
;----------From Protected Mode ----------;
FromProtected:
        pushf
        push ds
        push es
        push di
        push si

    mov ecx, 0xC0000080          ; Set the C-register to 0xC0000080, which is the EFER MSR.
    rdmsr                        ; Read from the model-specific register.
    or eax, 1 << 8               ; Set the LM-bit which is the 9th bit (bit 8).
    wrmsr                        ; Write to the model-specific register.

    mov eax, cr0                 ; Set the A-register to control register 0.
    or eax,1 << 31               ; Set the PG-bit, which is the 32nd bit (bit 31).
    mov cr0, eax                 ; Set control register 0 to the A-register.

        pop si
        pop di
        pop es
        pop ds
        popf
ret

;----------Main Program ----------;

Main32:

mov ecx, 2000d
mov eax, 0x0b8000 ; note 32 bit offset
clear_scr:
   mov bx, 0x0   ; attrib/char of smiley
   mov word [ds:eax], bx
   add eax,2
loop clear_scr

call CheckCPU
call DetectLong
call EnablePaging
call FromProtected


lgdt [GDT64.Pointer]         ; Load the 64-bit global descriptor table.
jmp GDT64.Code:Realm64       ; Set the code segment and enter 64-bit long mode.

[BITS 64]

Realm64:
	hlt
	cli                           ; Clear the interrupt flag.
	mov ax, GDT64.Data            ; Set the A-register to the data descriptor.
	mov ds, ax                    ; Set the data segment to the A-register.
	mov es, ax                    ; Set the extra segment to the A-register.
	mov fs, ax                    ; Set the F-segment to the A-register.
	mov gs, ax                    ; Set the G-segment to the A-register.
	;mov edi, 0xB8000              ; Set the destination index to 0xB8000.
	;mov rax, 0x1F201F201F201F20   ; Set the A-register to 0x1F201F201F201F20.
	;mov ecx, 500                  ; Set the C-register to 500.
	;rep movsq                     ; Clear the screen.
	;hlt                           ; Halt the processor.


	mov edi, 0xB8000
	mov rcx, 500                      ; Since we are clearing QWORDs over here, we put the count as Count/4.
	mov rax, 0x1F201F201F201F20       ; Set the value to set the screen to: Blue background, white foreground, blank spaces.
	rep stosq                         ; Clear the entire screen. 

	hlt
I then compile and build with the following commands:

Code: Select all

nasm bootloader.asm -f bin -o bootloader.bin
nasm kernel.asm -f bin -o kernel.bin
cat bootloader.bin kernel.bin /dev/zero | dd bs=512 count=2880 of=floppy.img
I then run it with qemu-system-x86_64

When I put a hlt command just right after lgdt [GDT64.Pointer] , the code works correctly and the screen is cleared and then halt.
If I remove the hlt to make the code continue to jump to the 64 bit code, the qemu keep on rebooting.

I think that I am jumping to a wrong address but I don't know why.

One more thing is that I have the same code all in one file, with minor modifications, and it works well without any problems.

Any help will be most appreciated.

Thanks a lot
Karim.

Re: Error Loading 64-bit Code From My Own Custom Boot Loader

Posted: Sat Nov 02, 2013 2:26 pm
by Nable
Try using Bochs, it has much better debug output than others.

Re: Error Loading 64-bit Code From My Own Custom Boot Loader

Posted: Sun Nov 03, 2013 12:14 am
by egos
"org 1000h"?

Re: Error Loading 64-bit Code From My Own Custom Boot Loader

Posted: Sun Nov 03, 2013 8:45 am
by kemosparc
egos wrote:"org 1000h"?

Thanks a lot, much appreciated
That Fixed it

Karim