My operating system is Ubuntu. I am trying to follow through a tutorial building a kernel..Even though i ve built the kernel, when i try to compile a simple C program that prints an 'X' on the top left corner of the screen in qemu,the screen just loops infinitely between some Real-Mode print commands, without ever landing in protected mode.. The program includes loading some extra sectors with disk_load.asm, then switching to 32 bit protected mode with switch_to_pm, and then executing the C code
Here is the code.
boot_sect.asm
;boot sector that boots a C kernel in 32-bit protected mode
[org 0x7c00]
KERNEL_OFFSET equ 0x1000 ;This is the memory offset to which we will load our kernel
mov [BOOT_DRIVE],dl ;BIOS stores our boot drive in DL,so it's
;best to remember this for later.
mov bp, 0x9000;Set-up the stack.
mov sp,bp
mov bx, MSG_REAL_MODE ;Announce that we are starting
call print_string ;booting from 16-bit real mode
call load_kernel ;Load our kernel
call switch_to_pm;Switch to protected mode,from which
;we will not return
jmp $
Include our useful,hard-earned routines
%include "print_string.asm"
%include "disk_load.asm"
%include "gdt.asm"
;%include "print_string_pm.asm"
%include "switch_to_pm.asm"
[bits 16]
load_kernel
load_kernel:
;mov bx, MSG_LOAD_KERNEL ;Print a message to say we are loading the kernel
;call print_string
mov bx, KERNEL_OFFSET ;Set-up parameters for our disk_load routine,so
mov dh, 15 ;that we load the first 15 sectors(excluding
mov dl, [BOOT_DRIVE] ;the boot sector)from the boot disk(i.e.our
call disk_load ;kernel code)to address KERNEL_OFFSET
ret
[bits 32]
;This is where we arrive after switching to and initialising protected mode
BEGIN_PM:
;mov ebx, MSG_PROT_MODE ;Use our 32-bit print routine to
;call print_string_pm ;announce we are in protected mode
call KERNEL_OFFSET ;Now jump to the address of our loaded
;kernel code,assume the brace position,
;and cross your fingers.Here we go!
jmp $ ;Hang.
;Globalvariables
BOOT_DRIVE db 0
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
;MSG_PROT_MODE db "Successfully landed in 32-bit Protected Mode", 0
MSG_LOAD_KERNEL db "Loading kernel into memory.", 0
;Bootsectorpadding
times 510-($-$$) db 0
dw 0xaa55
disk_load.asm
;load DH sectors to ES:BX from drive DL
;works for floppies
disk_load:
push dx
;Store DX on stack so later we can recall
;how many sectors were request to be read,
;even if it is altered in the mean time
mov ah, 0x02 ;BIOS read sector function
mov al, dh ;Read DH sectors
mov ch, 0x00 ;Select cylinder 0
mov dh, 0x00 ;Select head 0
mov cl, 0x02 ;Start reading from second sector(i.e.
;after the boot sector)
int 0x13 ;BIOS interrupt
jc disk_error ;Jump if error(i.e.carryflagset)
pop dx ;Restore DX from thestack
cmp dh, al;ifAL(sectorsread)!=DH(sectorsexpected)
jne disk_error ;display error message
ret
disk_error :
mov bx, DISK_ERROR_MSG
call print_string
jmp $ ;Variables
DISK_ERROR_MSG db "Disk read error!", 0
switch_to_pm.asm
switch_to_pm:
cli ;We must switch of interrupts until we have cli->clear interrupt
;set-up the protected mode interrupt vector
;otherwise interrupts will run riot
lgdt [gdt_descriptor] ;Load our global descriptor table,which defines
;the protected mode segments(e.g.for code and data)
mov eax,cr0 ;To make the switch to protected mode,we set
or eax, 0x1 ;the first bit of CR0,a control register
mov cr0,eax ;update register, we cannot set the bit directly on the register
jmp CODE_SEG:init_pm ;Make a far jump(i.e.to a new segment)to our 32-bit
;code.This also forces the CPU to flush its cache of
;pre-fetched and real mode decoded instructions,which can
;cause problems. we can use the or
;instruction to include certain bits into a value (i.e. without
;disturbing any other bits that, for some important reason, may have been set
;already in the control register)
[bits 32]
;Initialise registers and the stack once in PM.
init_pm:
mov ax, DATA_SEG ;Now in PM,our old segments are meaningless,
mov ds,ax ;so we point our segment registers to the
mov ss,ax ;data selector we defined in our GDT
mov es,ax
mov fs,ax
mov gs,ax
mov ebp, 0x90000 ;position so it is right
mov esp,ebp ;at the top of the free space.
call BEGIN_PM ;Finally,call somewell-known label7
the GDT code, that contains the gdt_descriptor
gdt.asm
; GDT
gdt_start:
gdt_null: ;the mandatory null descriptor
dd 0x0 ;'dd'means define double word(i.e.4bytes)
dd 0x0
gdt_code: ;the code segment descriptor
;base=0x0,limit=0xfffff ,
;1st flags:(present)1(privilege)00(descriptortype)1->1001b
;type flags:(code)1(conforming)0(readable)1(accessed)0->1010b
;2nd flags:(granularity)1(32-bitdefault)1(64-bitseg)0(AVL)0->1100b
dw 0xffff ;Limit(bits0-15)
dw 0x0 ;Base(bits0-15)
db 0x0 ;Base(bits16-23)
db 10011010b ;1st flags,type flags
db 11001111b ;2n dflags,Limit(bits16-19)
db 0x0 ;Base(bits24-31)
gdt_data: ;the data segment descriptor
;Same as code segment except for the type flags:
;type flags:(code)0(expanddown)0(writable)1(accessed)0->0010b
dw 0xffff ;Limit(bits0-15)
dw 0x0 ;Base(bits0-15)
db 0x0 ;Base(bits16-23)
db 10010010b ;1stflags,type flags
db 11001111b ;2ndflags,Limit(bits16-19)
db 0x0 ;Base(bits24-31)
gdt_end: ;The reason for putting a label at the end of the
;GDT is so we can have the assembler calculate
;the size of the GDT for the GDT decriptor(below)
;GDT descriptior
gdt_descriptor:
dw gdt_end - gdt_start - 1 ;Size of our GDT,always less one
;of the true size
dd gdt_start ;Start address of our GDT
;Define some handy constants for the GDT segment descriptor offsets,which
;are what segment registers must contain when in protected mode.For example,
;when we set DS=0x10 in PM,the CPU knows that we mean it to use the
;segment described at offset 0x10(i.e.16bytes)in our GDT,which in our
;case is the DATA segment(0x0->NULL;0x08->CODE;0x10->DATA)
CODE_SEG equ gdt_code - gdt_start -10;-10 so it is withing memory limits
DATA_SEG equ gdt_data - gdt_start -10;
print_string.asm
print_string:
pusha
mov ah, 0x0e
loop:
mov al, [bx]
cmp al, 0
je return
int 0x10
inc bx
jmp loop
return:
popa
ret
That is the C program that prints an 'X'
kernel.c
void main() {
//Create a pointer to a char,and point it to the first text cell of
//video memory(i.e.the top-left of the screen)
char* video_memory = (char *) 0xb800;
//At the address pointed to by video_memory,store the character'X'
//(i.e.display 'X' in the top-left of the screen).
*video_memory = 'X';
}
In order to make the C code, raw machine code we type in the directory we have saved the kernel.c file
$gcc -ffreestanding -c kernel.c -o kernel.o
$ld -o kernel.bin -Ttext 0x1000 kernel.o --oformat binary
then in order to create the binary file for qemu
$nasm boot_sect.asm -f bin -o boot_sect.bin
and finally
$cat bootsect.bin kernel.bin > os-image
to execute the code as a floppy
$qemu -fda os-iamge
and now instead of printing an 'X' it just loops infinitely, printing only the Real Mode strings("Started in 16 bit Real-Mode"and "Loading Kernel into memory.", and not entering 32bit protected mode. I tried running every piece of code in nasm one by one,and then adding every individual piece of code together, and it seemed like the loop began when the switch_to_pm piece of code was added.Why is that? (i think something is faulty with switch_to_pm code) Thank you a lot.