Page 1 of 1

Qemu infinite restart after giving control to the kernel

Posted: Mon Dec 20, 2021 7:22 pm
by liwinux
Hi,

I'm facing a problem where Qemu keeps restarting because of (I guess) an invalid address.. I've successfully managed to get into protected mode and my gdt is loaded correctly.
Here is my bootloader:

Code: Select all

.intel_syntax noprefix
.code16 # use 16 bits
.global _start
.global _init
.text

_start:
  .space 80, 0 # Some BIOSes need a BPB, therefore we fill up space for a fake one
  jmp 0x0000, _init # in case BIOS set cs to 0x7c00. We work with cs:ip

_init:

  cld
  mov bp, 0x9000
  mov sp, bp

  xor ax, ax
  mov ds, ax
  mov ss, ax
  mov es, ax

  movb [BOOT_DRIVE], dl

  mov bx, offset flat:start_16_str
  call print

  mov bx, offset flat:read_disk_str
  call print

  mov dh, 0x1
  mov bx, 0x7e00 # memory location to load disk to
  call load_disk

  mov bx, offset flat:read_disk_success_str
  call print


  ljmp 0x0000:0x7e00


.include "print_16.S"
.include "read_disk.S"

start_16_str:
  .asciz "Starting in 16-bit mode"
read_disk_str:
  .asciz "Loading disk into memory"

read_disk_success_str:
  .asciz "Loaded disk successfully !"


.set BOOT_DRIVE, 0
.space 510-(.-_start), 0 # add zeroes to make it 510 bytes long
.word 0xaa55 # magic bytes that tell BIOS that this is bootable

secondstage:
  call .check_CPUID
  mov bx, offset flat:enable_a20_gate_str
  call print

  mov ax, 0x2401
  int 0x15
  call switch_to_protected_mode


.check_CPUID:
  pusha

	pushfd
	pop eax
	mov ecx, eax
	xor eax, (1 << 21)
	push eax
	popfd
	pushfd
	pop eax
	push ecx
	popfd
	xor eax, ecx
	jz .no_CPUID

  movb [cpuid_support], 1
  mov bx, offset flat:cpuid_success_str
  call print

  mov eax, 0x80000000
	cpuid
	cmp eax, 0x80000001
	jb .lm_error

  jmp .check_lm


.check_lm:
  mov eax, 0x80000001 	#check if CPU supports Long Mode, abort if not
	cpuid
	test edx, (1 << 29)
	jz .lm_error

  movb [long_mode_support], 1
  mov ebx, offset flat:lm_success_str
  call print

  jmp lm_end

.no_CPUID:
  movb [cpuid_support], 0
  mov ebx, offset flat:cpuid_error_str
  call print
  jmp lm_end

.lm_error:
  movb [long_mode_support], 0
  mov ebx, offset flat:lm_error_str
  call print
  jmp lm_end

lm_end:
  popa
  ret

lm_error_str:
  .asciz "ERROR: CPU does not support Long Mode"

lm_success_str:
  .asciz "CHECK: CPU support Long mode "

cpuid_error_str:
  .asciz "ERROR: CPU does not support CPUID"

cpuid_success_str:
  .asciz "CHECK: CPU support CPUID"

enable_a20_gate_str:
  .asciz "WARNING: Enabling A20 gate"

.include "gdt.S"
.include "32bit_switch.S"
.include "32bit-print.S"

.code32
BEGIN_PM:

  mov ebx, offset flat:MSG_PROT_MODE
  call print_string_pm
  call 0x10000 # Here is where it fails

  jmp .

MSG_PROT_MODE:
.asciz "[SUCCESS] Giving control to the Kernel !"


.set cpuid_support, 0
.set long_mode_support, 0
Notice where it crashes :

Code: Select all

.code32
BEGIN_PM:

  mov ebx, offset flat:MSG_PROT_MODE
  call print_string_pm
  call 0x10000

  jmp .
Also, here is where I switch to protected mode :

Code: Select all

.intel_syntax noprefix
.code16 # use 16 bits
switch_to_protected_mode:
    cli
    lgdt [gdt_descriptor]
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax
    ljmp CODE_SEG:init_pm
    
.code32
init_pm:
    
    mov ax, DATA_SEG
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    
    mov ebp, 0x9000
    mov esp, ebp

    call BEGIN_PM

My linker script for the bootloader is this one (nothing too complicated) :

Code: Select all

ENTRY(_start)

SECTIONS
{
	. = 0x7C00;

	.text : {
		*(.text*)
	}
    
	.rodata : {
		*(.rodata*)
	}

	.data : {
		*(.data*)
	}

	.bss : {
		*(.bss*)
	}
}
My kernel entry which calls the main function in another file :

Code: Select all

.code32
.extern main

call main

jmp .
and it's linker script with .text at 0x10000

Code: Select all

ENTRY(main)

SECTIONS
{
	. = 0x10000;

    .text : {
        KEEP(*(.text*))
    }

}
Now, I don't really understand why this shouldn't work as my kernek is loader at 0X10000 and in my bootloader I call this address. Also worth to note, in order to link my bootloader and my kenrel, I'm just using cat.

Thanks in advance !

Re: Qemu infinite restart after giving control to the kernel

Posted: Mon Dec 20, 2021 8:16 pm
by Octocontrabass
liwinux wrote:(I guess)
Don't guess! Add "-d int" and "-no-reboot" to your QEMU command line to get more information. (You may also need to disable KVM.)

Re: Qemu infinite restart after giving control to the kernel

Posted: Tue Dec 21, 2021 4:48 am
by liwinux
Octocontrabass wrote:
liwinux wrote:(I guess)
Don't guess! Add "-d int" and "-no-reboot" to your QEMU command line to get more information. (You may also need to disable KVM.)
The truth is that I'm quite not understanding what Qemu returns as output ...:

Code: Select all

check_exception old: 0xffffffff new 0x6
     0: v=06 e=0000 i=0 cpl=0 IP=0008:0009fc42 pc=0009fc42 SP=0010:00008ff8 env->regs[R_EAX]=00000010
EAX=00000010 EBX=00007fa3 ECX=00000000 EDX=00000180
ESI=00000000 EDI=00000000 EBP=00009000 ESP=00008ff8
EIP=0009fc42 EFL=00000086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00007f2a 00000017
IDT=     00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000010 CCD=000000c3 CCO=ADDB    
EFER=0000000000000000
check_exception old: 0xffffffff new 0xd
     1: v=0d e=0032 i=0 cpl=0 IP=0008:0009fc42 pc=0009fc42 SP=0010:00008ff8 env->regs[R_EAX]=00000010
EAX=00000010 EBX=00007fa3 ECX=00000000 EDX=00000180
ESI=00000000 EDI=00000000 EBP=00009000 ESP=00008ff8
EIP=0009fc42 EFL=00000086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00007f2a 00000017
IDT=     00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000010 CCD=000000c3 CCO=ADDB    
EFER=0000000000000000
check_exception old: 0xd new 0xd
     2: v=08 e=0000 i=0 cpl=0 IP=0008:0009fc42 pc=0009fc42 SP=0010:00008ff8 env->regs[R_EAX]=00000010
EAX=00000010 EBX=00007fa3 ECX=00000000 EDX=00000180
ESI=00000000 EDI=00000000 EBP=00009000 ESP=00008ff8
EIP=0009fc42 EFL=00000086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00007f2a 00000017
IDT=     00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000010 CCD=000000c3 CCO=ADDB    
EFER=0000000000000000
check_exception old: 0x8 new 0xd
Nothing refers to 0x10000 which is where my kernel should right after my bootloader... I case you are wondering, I've putted an infinite loop right before the call to my kernel and it doesn't reboot. So clearly the problem is the specific call to my kernel.

More infos I can give is that, thanks to gdb I can see that I'm successfully jumping to 0x10000, but there are no instructions at all at this address. So I'm wondering if it's a problem with my linker script for the kernel part. I wish I could use only one linker script for the bootloader and the kernel and therefore having to avoid using cat to bind both of them

Re: Qemu infinite restart after giving control to the kernel

Posted: Tue Dec 21, 2021 5:09 am
by Klakap
I can not see in your code where are you reading your kernel from disk to address 0x10000.

Re: Qemu infinite restart after giving control to the kernel

Posted: Tue Dec 21, 2021 5:26 am
by liwinux
Klakap wrote:I can not see in your code where are you reading your kernel from disk to address 0x10000.
Actually it's right over here, I'm loading 1 sector after my bootloader :

Code: Select all

mov dh, 0x1
  mov bx, 0x7e00 # memory location to load disk to
  call load_disk


and then after loading the gdt, I'm jumping to the kernel

Code: Select all

.code32
BEGIN_PM:

  mov ebx, offset flat:MSG_PROT_MODE
  call print_string_pm
  call 0x10000 # Here is where it fails
  jmp .

Re: Qemu infinite restart after giving control to the kernel

Posted: Tue Dec 21, 2021 6:02 am
by Klakap
So it do not work because you do not load your code. On 0x10000 there are no commands for processor, and it immediately cause some fault. You have to write code for loading your kernel from disk.

Re: Qemu infinite restart after giving control to the kernel

Posted: Tue Dec 21, 2021 6:15 am
by liwinux
Klakap wrote:So it do not work because you do not load your code. On 0x10000 there are no commands for processor, and it immediately cause some fault. You have to write code for loading your kernel from disk.
But isn’t what I have done with the interrupt 0x13 ? I don’t know how else I could do to load my code into memory… And if I’m not wrong, my second linker script tells to place my first kernel I striction at 0x10000 so technically there must be something over there right ?

Re: Qemu infinite restart after giving control to the kernel

Posted: Tue Dec 21, 2021 7:13 am
by Klakap
Yes, you load your code with interrupt 13h. But after start, BIOS load only one sector, bootloader to memory 0x7C00. You in your code call function to read second sector to memory 0x7E00. But it is not your kernel, it is only extended part of your bootloader. So you have to call your function for reading from disk again, with parameters for reading enough sectors where you have your kernel in disk to memory on 0x10000.

Example, if you have your kernel in sector 2, immediately after your extended bootloader, your reading code can look like:

Code: Select all

 mov ax, 0x1000
 mov es, ax ;code will be loaded to segment 0x1000, what mean address 0x10000
 mov bx, 0 ;code will be loaded to offset 0, what means that it will be on 0x1000*0x10+0=0x10000

 mov ah, 0x2 ;read function of int 13h
 mov al, 72 ;72 sectors is 36 KB, it is much more than enough to load your kernel
 mov ch, 0 ;cylinder 0
 mov dh, 0 ;head 0
 mov cl, 3 ;sector 3
 ;dl is already set

 int 13h ;this read 72 sectors from LBA sector 2 to memory 0x10000
After this, you have your code in memory and jump should be succesful.

Re: Qemu infinite restart after giving control to the kernel

Posted: Tue Dec 21, 2021 7:46 am
by liwinux
Klakap wrote:Yes, you load your code with interrupt 13h. But after start, BIOS load only one sector, bootloader to memory 0x7C00. You in your code call function to read second sector to memory 0x7E00. But it is not your kernel, it is only extended part of your bootloader. So you have to call your function for reading from disk again, with parameters for reading enough sectors where you have your kernel in disk to memory on 0x10000.

Example, if you have your kernel in sector 2, immediately after your extended bootloader, your reading code can look like:

Code: Select all

 mov ax, 0x1000
 mov es, ax ;code will be loaded to segment 0x1000, what mean address 0x10000
 mov bx, 0 ;code will be loaded to offset 0, what means that it will be on 0x1000*0x10+0=0x10000

 mov ah, 0x2 ;read function of int 13h
 mov al, 72 ;72 sectors is 36 KB, it is much more than enough to load your kernel
 mov ch, 0 ;cylinder 0
 mov dh, 0 ;head 0
 mov cl, 3 ;sector 3
 ;dl is already set

 int 13h ;this read 72 sectors from LBA sector 2 to memory 0x10000
After this, you have your code in memory and jump should be succesful.
That makes more sense indeed... So, If I undersand correctly what are saying, I should first load my extended bootloader, jump to it like I'm already doing. From there then, I load my kernel at offset 0X10000 before going into protected mode and after loading the gdt I jump to it. And in order to achieve that, I have to place my kernel at the 1536th byte since first 512 is the bootloader and 1024th is the extended bootloader right ?

Re: Qemu infinite restart after giving control to the kernel

Posted: Tue Dec 21, 2021 8:11 am
by Klakap
Yes, I use similar way in my bootloader.
I have to place my kernel at the 1536th byte, right?
Yes. If you use linux, dd wil calculate this for you. Here is part of my compile script:

Code: Select all

dd if=/dev/zero of=images/bleskos.hdd bs=1024 count=1440 #create new disk image
dd if=bootloader/bootloader.bin of=images/bleskos.hdd conv=notrunc seek=0 #this file is 1 sector bootloader + 4 sectors extended bootloader, so I write it from sector LBA 0
dd if=images/bleskos.bin of=images/bleskos.hdd conv=notrunc seek=5 #this is my whole code, I write it from LBA 5
After this in bleskos.hdd in LBA 0 is bootloader, in LBA 1 - LBA 4 is extended bootloader and from LBA 5 is code of my OS.

Re: Qemu infinite restart after giving control to the kernel

Posted: Tue Dec 21, 2021 8:55 am
by liwinux
Klakap wrote:Yes, I use similar way in my bootloader.
I have to place my kernel at the 1536th byte, right?
Yes. If you use linux, dd wil calculate this for you. Here is part of my compile script:

Code: Select all

dd if=/dev/zero of=images/bleskos.hdd bs=1024 count=1440 #create new disk image
dd if=bootloader/bootloader.bin of=images/bleskos.hdd conv=notrunc seek=0 #this file is 1 sector bootloader + 4 sectors extended bootloader, so I write it from sector LBA 0
dd if=images/bleskos.bin of=images/bleskos.hdd conv=notrunc seek=5 #this is my whole code, I write it from LBA 5
After this in bleskos.hdd in LBA 0 is bootloader, in LBA 1 - LBA 4 is extended bootloader and from LBA 5 is code of my OS.
Many thanks, I managed to get my bootloader working by loading my kernel at 0x8000 for simplicity since my first stage is 512 bytes and so is my second stage so 0x7c00 + 1024 = 0x8000 <-- where my kernel will me loaded in memory !