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
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
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
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.