Single stage bootloader failing to load the linux kernel
Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Single stage bootloader failing to load the linux kernel
Hello everyone,
As a hobby, I'm trying to build a single stage bootloader of a recent version of a linux kernel. My final goal is to have a signle binary containing my bootloader and the bzImage of linux, then use qemu to test it, so I know in advance the place of bzImage inside my final binary that I can use later for loading sectors using LBA. An example of my Makefile
I followed what was documented on the official linux boot protocol, I started by loading the linux boot sector at the address 0x10000 to facilitate the calculation of the linear address, then reading the size of the setup sectors then loading them. I used the bios interrupt 0x13 for this matter.
Then I have populated the header with the required values ( the heap end pointer, the address of cmd_line).
When it is time to load the rest of the kernel ( the size is found in the syssize entry in the header ) at the address 0x100000. Since I'm using the real mode, I wanted to jump temporarily to the protected mode, load the rest of the linux kernel and switch back to the real mode. I have used 16 bit of code/data segments for this purpose to reload the segment selectors properly and avoid having any 32 bit related attributes. When switching back to real mode, I have tested that the bios interrupts are working as expected to make sure that everything was reloaded correctly.
https://wiki.osdev.org/Real_Mode#Switch ... _Real_Mode
However, when it is time to make the jump to the linux kernel, it was not working, when debugging and setting up a breakpoint just before the jump, the breakpoint was hit like it is in an infinite loop, the display one the qemu kept blinking. I tried to debug and verifying the code and the values injected into the headers, everything seems to be fine.
I was expecting to have a kernel panic or having some error messages, but not a qemu screen blinking and reloading. This is the first time I'm doing a switch from protected mode to real mode, I believe this part is very tricky, I followed what was published on the osdev wiki, and the execution of a bios interrupt was confirming that we are in the real mode again.
I'm joining my code here if someone can help me out, I will be grateful, it has been a while I'm stuck in this problem.
I have commented the code for the parts that are not obvious. Some instructions regarding the sectors to load are not that clear but because I have run the code and checked the values, For example:
number of setup sectors is: 39
I have used the linux kernel version: 6.16
NOTE: I know that I can use the bios interrupt 0x15 when being in the real mode, I have found this idea while doing my research, but I think understanding and fixing the problem that I have is a great way to strenghen my knowledge.
Many thanks.
As a hobby, I'm trying to build a single stage bootloader of a recent version of a linux kernel. My final goal is to have a signle binary containing my bootloader and the bzImage of linux, then use qemu to test it, so I know in advance the place of bzImage inside my final binary that I can use later for loading sectors using LBA. An example of my Makefile
Code: Select all
as -g boot.s -o boot.o
ld -g -Ttext 0x7C00 ./boot.o -o boot.elf
objcopy -O binary ./boot.elf boot.bin
dd if=./linux-6.16/arch/x86_64/boot/bzImage >> boot.bin
qemu-system-x86_64 -hda ./boot.bin
Then I have populated the header with the required values ( the heap end pointer, the address of cmd_line).
When it is time to load the rest of the kernel ( the size is found in the syssize entry in the header ) at the address 0x100000. Since I'm using the real mode, I wanted to jump temporarily to the protected mode, load the rest of the linux kernel and switch back to the real mode. I have used 16 bit of code/data segments for this purpose to reload the segment selectors properly and avoid having any 32 bit related attributes. When switching back to real mode, I have tested that the bios interrupts are working as expected to make sure that everything was reloaded correctly.
https://wiki.osdev.org/Real_Mode#Switch ... _Real_Mode
However, when it is time to make the jump to the linux kernel, it was not working, when debugging and setting up a breakpoint just before the jump, the breakpoint was hit like it is in an infinite loop, the display one the qemu kept blinking. I tried to debug and verifying the code and the values injected into the headers, everything seems to be fine.
I was expecting to have a kernel panic or having some error messages, but not a qemu screen blinking and reloading. This is the first time I'm doing a switch from protected mode to real mode, I believe this part is very tricky, I followed what was published on the osdev wiki, and the execution of a bios interrupt was confirming that we are in the real mode again.
I'm joining my code here if someone can help me out, I will be grateful, it has been a while I'm stuck in this problem.
Code: Select all
.code16
.global _start
.equ BASE_SEG, 0x1000
_start:
cli
xor %ax, %ax
mov %cs, %ax
mov %ax, %ds
mov $BASE_SEG, %ax
mov %ax, %es
xor %bx, %bx
call _load_boot_sector
jmp _switch_pm
_load_boot_sector:
mov 0ドルx02, %ah
mov 1,ドル %al
mov 0,ドル %ch
mov 2,ドル %cl
mov 0,ドル %dh
mov 0ドルx80, %dl
int 0ドルx13
jc _disk_error
jmp _load_setup_sectors
_load_setup_sectors:
mov 0ドルx200, %bx
mov 0ドルx02, %ah
movb %es:0x1F1, %al
mov 0ドルx3, %cl
int 0ドルx13
jc _disk_error
_init_kernel_header:
mov $BASE_SEG, %ax
mov %ax, %es
movb 0ドルxFF, %es:0x210
// set heap in use bit
orb 0ドルx80, %es:0x211
// heap end_ptr
movw 0ドルxDE00, %es:0x224
// initramfs: not initramfs for now
movw 0ドルx0, %es:0x218
mov 0ドルx0, %es:0x21c
// cmd_line_ptr
movl 0ドルx1E000, %es:0x228
_copy_cmd_line:
// copy the cmd_line to its location: 0x1E000 (%es:%di)
mov $cmd_line_size, %cx
mov $cmd_line, %si
mov 0ドルxE000, %di
rep movsb
ret
_disk_error:
call _print_error
jmp .
_print_error:
mov 0ドルx0E, %ah
mov $'E', %al
mov 0,ドル %bh
mov 0,ドル %bl
int 0ドルx10
ret
_switch_pm:
lgdt gdt_desc
mov %cr0, %eax
or 0ドルx1, %eax
mov %eax, %cr0
mov 2,ドル %al
out %al, 0ドルx92
ljmp 0ドルx8, $_start_32
.code32
_start_32:
mov 0ドルx10, %ax
mov %ax, %ds
mov %ax, %ss
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov 0ドルx90000, %esp
_load_lba:
// load the syssize from the header
mov %es:0x101F4, %edx
// the syssize is the number of 16 bytes => the number of sectors is (syssize * 16) /512
shr 5,ドル %edx
mov %dx, %bx
// address of the dest buffer in memory
mov 0ドルx100000, %edi
// init
mov 0ドルx1F6, %dx
mov 0ドルx40, %al
out %al, %dx
// send sector count high byte
mov 0ドルx1F2, %dx
mov %bh, %al
out %al, %dx
// send lba high byte
mov 0ドルx1F3, %dx
mov 0,ドル %al
out %al, %dx
mov 0ドルx1F4, %dx
mov 0,ドル %al
out %al, %dx
mov 0ドルx1F5, %dx
mov 0,ドル %al
out %al, %dx
// send sector count low byte
mov 0ドルx1F2, %dx
mov %bl, %al
out %al, %dx
// send lba low byte
mov 0ドルx1F3, %dx
movb %es:0x101F1, %al
// add 3 sectors: sector of my bootloader, linux boot sector and one more sector according to the documentation
add 3,ドル %al
out %al, %dx
mov 0ドルx1F4, %dx
mov 0,ドル %al
out %al, %dx
mov 0ドルx1F5, %dx
mov 0,ドル %al
out %al, %dx
// send read command
mov 0ドルx1F7, %dx
mov 0ドルx24, %al
out %al, %dx
jmp _wait_loop
_wait_loop:
mov 0ドルx1F7, %dx
in %dx, %al
testb 0ドルx08, %al
jz _wait_loop
jmp _read
_read:
mov 256,ドル %cx
mov 0ドルx1F0, %dx
rep insw
_sleep:
mov 0ドルx3F6, %dx
in %dx, %al
in %dx, %al
in %dx, %al
in %dx, %al
dec %bl
cmp 0,ドル %bl
je _reset_16
jmp _wait_loop
// use a 16 bit data/code segment to go back to the real mode
_reset_16:
cli
mov 0ドルx20, %eax
mov %eax, %ds
mov %eax, %ss
mov %eax, %es
mov %eax, %fs
mov %eax, %gs
ljmp 0ドルx18, $_switch_rm
.code16
_switch_rm:
mov %cr0, %eax
and $~1, %eax
mov %eax, %cr0
ljmp 0ドルx0000, $_start_16
_start_16:
// reload the segments with the base seg value: 0x1000
mov $BASE_SEG, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %ss
mov %ax, %gs
mov %ax, %fs
mov 0ドルxE000, %esp
// Print on the screen 'A' using bios interrupts to make sure that the real mode is working
mov 0ドルx0E, %ah
mov $'A', %al
mov 0,ドル %bh
mov 0,ドル %bl
int 0ドルx10
ljmp 0ドルx1020, 0ドルx0
gdt:
gdt_null:
.long 0x0
.long 0x0
gdt_code:
.word 0xFFFF
.word 0x0
.byte 0x0
.byte 0x9A
.byte 0xCF
.byte 0x0
gdt_data:
.word 0xFFFF
.word 0x0
.byte 0x0
.byte 0x92
.byte 0xCF
.byte 0x0
gdt_16_code:
.word 0xFFFF
.word 0x0
.byte 0x0
.byte 0x9A
.byte 0x8F
.byte 0x0
gdt_16_data:
.word 0xFFFF
.word 0x0
.byte 0x0
.byte 0x92
.byte 0x8F
.byte 0x0
gdt_end:
gdt_desc:
.word gdt_end - gdt - 1
.long gdt
cmd_line:
.asciz "root=/dev/zero console=ttyS0"
.byte 0
cmd_line_size = . - cmd_line
.org 510
.word 0xAA55
number of setup sectors is: 39
I have used the linux kernel version: 6.16
NOTE: I know that I can use the bios interrupt 0x15 when being in the real mode, I have found this idea while doing my research, but I think understanding and fixing the problem that I have is a great way to strenghen my knowledge.
Many thanks.
- Octocontrabass
- Member
Member - Posts: 5968
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Single stage bootloader failing to load the linux kernel
Post by Octocontrabass »
[引用]
That sounds like a triple fault. Use "-no-reboot" to stop QEMU from rebooting while you're debugging it. Use "-d int" to see a log of the exceptions leading up to the triple fault.
- MichaelPetch
- Member
Member - Posts: 848
- Joined: Fri Aug 26, 2016 1:41 pm
- Libera.chat IRC: mpetch
Re: Single stage bootloader failing to load the linux kernel
Post by MichaelPetch »
I decided to use your code and a bzImage built with make defconfig on a 6.16 kernel. I built boot.bin with your commands and then ran it in QEMU with The result I got was a GPF (v=0d with error code e=0000) while executing address 0x100008. The full trace output I got from QEMU was:You can ignore the double fault v=08 output. I then loaded up QEMU and set a breakpoint at 0x100000 to see what code was executing. This is what I see when I did a `disas 0x100000,0x100100 /r`:When I step to the instruction at 0x100008 I get the GPF on `mov ss,eax` where EAX was set to 0. Setting ss to 0 should be allowed in 64-bit mode. I then did:This suggests you aren't in 64-bit mode at all and that you are in protected mode still thus the reason why `mov ss, eax` failed by loading a NULL selector into the stack segment. I had a similar experience running your code on BOCHS as well. The code in question is in fact that which is found in arch/x86/boot/compressed/head_64.S startup_64 code
Now I don't know what error you got and where. Getting your trace output from QEMU that shows the state at each exception/interrupt would be helpful. I just wanted to let you know what I saw.
Code: Select all
qemu-system-x86_64 -hda boot.bin -d int -M smm=off -no-shutdown -no-reboot
Code: Select all
check_exception old: 0xffffffff new 0xd
0: v=0d e=0000 i=0 cpl=0 IP=0010:0000000000100008 pc=0000000000100008 SP=0018:000000000001df90 env->regs[R_EAX]=0000000000000000
EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000000
ESI=00014770 EDI=00000000 EBP=00000000 ESP=0001df90
EIP=00100008 EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 00000000 00000000 00000000
CS =0010 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0000 00000000 00000000 00000000
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 00000000 00000000 00008200 DPL=0 LDT
TR =0020 00001000 00000067 00008900 DPL=0 TSS32-avl
GDT= 00014310 00000027
IDT= 00000000 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=わ0000000000000000 DR2=わ0000000000000000 DR3=わ0000000000000000
DR6=00000000ffff0ff0 DR7=わ0000000000000400
CCS=00010000 CCD=0001df90 CCO=CLR
EFER=0000000000000000
check_exception old: 0xd new 0xd
1: v=08 e=0000 i=0 cpl=0 IP=0010:0000000000100008 pc=0000000000100008 SP=0018:000000000001df90 env->regs[R_EAX]=0000000000000000
EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000000
ESI=00014770 EDI=00000000 EBP=00000000 ESP=0001df90
EIP=00100008 EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 00000000 00000000 00000000
CS =0010 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0000 00000000 00000000 00000000
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 00000000 00000000 00008200 DPL=0 LDT
TR =0020 00001000 00000067 00008900 DPL=0 TSS32-avl
GDT= 00014310 00000027
IDT= 00000000 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=わ0000000000000000 DR2=わ0000000000000000 DR3=わ0000000000000000
DR6=00000000ffff0ff0 DR7=わ0000000000000400
CCS=00010000 CCD=0001df90 CCO=CLR
EFER=0000000000000000
check_exception old: 0x8 new 0xd
Code: Select all
Dump of assembler code from 0x100000 to 0x100100:
=> 0x0000000000100000: fc cld
0x0000000000100001: fa cli
0x0000000000100002: 31 c0 xor eax,eax
0x0000000000100004: 8e d8 mov ds,eax
0x0000000000100006: 8e c0 mov es,eax
0x0000000000100008: 8e d0 mov ss,eax
0x000000000010000a: 8e e0 mov fs,eax
0x000000000010000c: 8e e8 mov gs,eax
0x000000000010000e: 48 8d 2d eb fd ff ff lea rbp,[rip+0xfffffffffffffdeb] # 0xffe00
0x0000000000100015: 8b 86 30 02 00 00 mov eax,DWORD PTR [rsi+0x230]
0x000000000010001b: ff c8 dec eax
0x000000000010001d: 48 01 c5 add rbp,rax
0x0000000000100020: 48 f7 d0 not rax
0x0000000000100023: 48 21 c5 and rbp,rax
0x0000000000100026: 48 81 fd 00 00 00 01 cmp rbp,0x1000000
0x000000000010002d: 73 07 jae 0x100036
0x000000000010002f: 48 c7 c5 00 00 00 01 mov rbp,0x1000000
0x0000000000100036: 8b 9e 60 02 00 00 mov ebx,DWORD PTR [rsi+0x260]
0x000000000010003c: 81 eb 00 a0 d6 00 sub ebx,0xd6a000
0x0000000000100042: 48 01 eb add rbx,rbp
0x0000000000100045: 48 8d a3 00 d0 d2 00 lea rsp,[rbx+0xd2d000]
0x000000000010004c: 48 8d 05 ad 7d d2 00 lea rax,[rip+0xd27dad] # 0xe27e00
0x0000000000100053: 48 01 40 02 add QWORD PTR [rax+0x2],rax
0x0000000000100057: 0f 01 10 lgdt [rax]
0x000000000010005a: 6a 10 push 0x10
0x000000000010005c: 48 8d 05 03 00 00 00 lea rax,[rip+0x3] # 0x100066
0x0000000000100063: 50 push rax
0x0000000000100064: 48 cb retfq
Code: Select all
(gdb) info registers cr0 efer
cr0 0x11 [ ET PE ]
efer 0x0 [ ]
Now I don't know what error you got and where. Getting your trace output from QEMU that shows the state at each exception/interrupt would be helpful. I just wanted to let you know what I saw.
Re: Single stage bootloader failing to load the linux kernel
Hello,
Thank you for your reply, I do appreciate the time and effort you have put to run my code.
When using qemu with logs, I have come to the same conclusion that a triple fault is happening.
The code I wrote is failing due to the following reasons apparently, correct me if I'm wrong:
- When jumping to the kernel the cpu was not in long mode, in my code, I have set the cpu even to real mode ( if you see the logs in qemu just before the first exceptio occured you will find the cr0 is set to 0x10 ) and I'm not sure how the cpu switched to protected mode.
- The boot params were not loaded, ESI is pointing to some memory but I don't think there is something there, maybe it was the last address used to do some memory copying before jumping to the kernel.
I'm still a beginner, trying to learn and understand how the bootloader internals work. I guess I will start by fixing the issues of my bootlaoder in order to make it functionnal for 32 bit kernel instead of 64 bit kernel, I think it will be relatively easier.
Thank you for your help.
Thank you for your reply, I do appreciate the time and effort you have put to run my code.
When using qemu with logs, I have come to the same conclusion that a triple fault is happening.
The code I wrote is failing due to the following reasons apparently, correct me if I'm wrong:
- When jumping to the kernel the cpu was not in long mode, in my code, I have set the cpu even to real mode ( if you see the logs in qemu just before the first exceptio occured you will find the cr0 is set to 0x10 ) and I'm not sure how the cpu switched to protected mode.
- The boot params were not loaded, ESI is pointing to some memory but I don't think there is something there, maybe it was the last address used to do some memory copying before jumping to the kernel.
I'm still a beginner, trying to learn and understand how the bootloader internals work. I guess I will start by fixing the issues of my bootlaoder in order to make it functionnal for 32 bit kernel instead of 64 bit kernel, I think it will be relatively easier.
Thank you for your help.
Re: Single stage bootloader failing to load the linux kernel
I have solved the errors previously mentioned, the changes aims at loading a 32 bit linux kernel, running the final file did not change except from using qemu-system-i386 instead of x86_64.
Howver I'm still facing the triple fault error, the logs from qemu are showing that the instruction causing the issue is localted at 0x102a4.
When using breakpoints and use disas with gdb, I found that this is the instruction that is failing
I loaded the bootparam at the address 0x90000 and copied the header at the address 0x90000 + 0x1F1, the first bytes are zeroed since the kernel will handle the loading of addional information regarding the screen info/acpi and other stuff.
But the instruction there is expecting to have a value of 0xaa5539d8 at 0x90000.
I recall that 0xaa55 is the magic number for the bootloader, however the rest of 2 bytes are unknown to me.
I searched the boot protocol over and over and searched through the linux kernel to find any mention to this but found none. I don't know from where this is coming out.
As a reminder the boot protocol states that before jumping to 0x10200 ( I loaded the kernel real mode at 0x10000 ) the esi register must point to the boot parameter which is located at 0x90000 for my case.
This is my code
Even when I force it in my code to put that "magic number" , the following instruction is failing inside the linux kernel, also in the above disassembled code, I feel it is werid the value placed in %edi at the instruction 0x102ae.
For now, I'm not using any initramfs for simplicity, so my expectation is to have a kernel panic at the end.
Any help regarding this. Many thanks in advance
Howver I'm still facing the triple fault error, the logs from qemu are showing that the instruction causing the issue is localted at 0x102a4.
Code: Select all
DR6=ffff0ff0 DR7=00000400
CCS=00000004 CCD=00000001 CCO=EFLAGS
EFER=0000000000000000
Servicing hardware INT=0x76
0: v=76 e=0000 i=0 cpl=0 IP=0010:0001029f pc=0001029f SP=0018:0001dffc env->regs[R_EAX]=00000018
EAX=00000018 EBX=00000000 ECX=00000000 EDX=0001e000
ESI=00090000 EDI=00000000 EBP=00000000 ESP=0001dffc
EIP=0001029f EFL=00000206 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0018 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= 00007d73 0000001f
IDT= 00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000018 CCD=0001e000 CCO=LOGICL
EFER=0000000000000000
check_exception old: 0xffffffff new 0xd
1: v=0d e=03b2 i=0 cpl=0 IP=0010:0001029f pc=0001029f SP=0018:0001dffc env->regs[R_EAX]=00000018
EAX=00000018 EBX=00000000 ECX=00000000 EDX=0001e000
ESI=00090000 EDI=00000000 EBP=00000000 ESP=0001dffc
EIP=0001029f EFL=00000206 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0018 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= 00007d73 0000001f
IDT= 00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000018 CCD=0001e000 CCO=LOGICL
EFER=0000000000000000
check_exception old: 0xd new 0xd
2: v=08 e=0000 i=0 cpl=0 IP=0010:0001029f pc=0001029f SP=0018:0001dffc env->regs[R_EAX]=00000018
EAX=00000018 EBX=00000000 ECX=00000000 EDX=0001e000
ESI=00090000 EDI=00000000 EBP=00000000 ESP=0001dffc
EIP=0001029f EFL=00000206 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0018 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= 00007d73 0000001f
IDT= 00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000018 CCD=0001e000 CCO=LOGICL
EFER=0000000000000000
check_exception old: 0x8 new 0xd
Triple fault
Code: Select all
0x0001029d: sti
0x0001029e: push %ds
0x0001029f: push 0ドルx66cb02a3
0x000102a4: cmpl 0ドルxaa5539d8,(%esi)
0x000102aa: pop %edx
0x000102ab: pop %edx
0x000102ac: jne 0x102c5
0x000102ae: mov 0ドルx63b939e0,%edi
0x000102b3: dec %ebp
0x000102b4: xor %ax,%ax
0x000102b7: sub %edi,%ecx
0x000102b9: shr 0ドルx2,%ecx
0x000102bc: rep stos %ax,%es:(%edi)
0x000102bf: callw 0x11c6
0x000102c3: add %al,(%eax)
0x000102c5: mov 0ドルx3eb,%ax
But the instruction there is expecting to have a value of 0xaa5539d8 at 0x90000.
I recall that 0xaa55 is the magic number for the bootloader, however the rest of 2 bytes are unknown to me.
I searched the boot protocol over and over and searched through the linux kernel to find any mention to this but found none. I don't know from where this is coming out.
As a reminder the boot protocol states that before jumping to 0x10200 ( I loaded the kernel real mode at 0x10000 ) the esi register must point to the boot parameter which is located at 0x90000 for my case.
This is my code
Code: Select all
.code16
.global _start
.equ BASE_SEG, 0x1000
_start:
cli
xor %ax, %ax
mov %cs, %ax
mov %ax, %ds # DS = CS for literals
mov $BASE_SEG, %ax
mov %ax, %es
xor %bx, %bx
_load_boot_sector:
mov 0ドルx02, %ah
mov 1,ドル %al
mov 0,ドル %ch
mov 2,ドル %cl
mov 0,ドル %dh
mov 0ドルx80, %dl
int 0ドルx13
jc _disk_error
jmp _load_setup_sectors
_load_setup_sectors:
mov 0ドルx200, %bx
mov 0ドルx02, %ah
movb %es:0x1F1, %al
mov 0ドルx3, %cl
int 0ドルx13
jc _disk_error
_init_kernel_header:
mov $BASE_SEG, %ax
mov %ax, %es
movb 0ドルxFF, %es:0x210
// heap
orb 0ドルx80, %es:0x211
// heap end_ptr
movw 0ドルxDE00, %es:0x224
// initramfs
movw 0ドルx0, %es:0x218
mov 0ドルx0, %es:0x21c
// cmd_line_ptr
movl 0ドルx1E000, %es:0x228
_copy_cmd_line:
mov $cmd_line_size, %cx
mov $cmd_line, %si
mov 0ドルxE000, %di
rep movsb
_init_zero_page:
mov 0ドルx9000, %ax
mov %ax, %es
mov 0ドルx1F1, %di
xor %ax, %ax
mov 0ドルx1000, %cx
rep stosw
_copy_header:
// copy the header from 0x101F1 to 0x901F1
// destination
mov 0ドルx1000, %ax
mov %ax, %ds
mov 0ドルx1F1, %si
// count
xor %ax, %ax
movb %ds:0x201, %al
add 0ドルx0202, %ax
sub 0ドルx1F1, %ax
mov %ax, %cx
// source
// ES is already set up to 0x9000
mov 0ドルx1F1, %di
rep movsb
jmp _switch_pm
_disk_error:
call _print_error
jmp .
_print_error:
mov 0ドルx0E, %ah
mov $'E', %al
mov 0,ドル %bh
mov 0,ドル %bl
int 0ドルx10
ret
_switch_pm:
xor %ax, %ax
mov %ax, %ds
lgdt gdt_desc
mov %cr0, %eax
or 0ドルx1, %eax
mov %eax, %cr0
mov 2,ドル %al
out %al, 0ドルx92
ljmp 0ドルx10, $_start_32
.code32
_start_32:
mov 0ドルx18, %ax
mov %ax, %ds
mov %ax, %ss
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov 0ドルx1E000, %esp
_load_lba:
// loop control var
mov %es:0x101F4, %edx
shr 5,ドル %edx
mov %dx, %bx
// address of the dest buffer in memory
mov 0ドルx100000, %edi
// init
mov 0ドルx1F6, %dx
mov 0ドルx40, %al
out %al, %dx
// send sector count high byte
mov 0ドルx1F2, %dx
mov %bh, %al
out %al, %dx
// send lba high byte
mov 0ドルx1F3, %dx
mov 0,ドル %al
out %al, %dx
mov 0ドルx1F4, %dx
mov 0,ドル %al
out %al, %dx
mov 0ドルx1F5, %dx
mov 0,ドル %al
out %al, %dx
// send sector count low byte
mov 0ドルx1F2, %dx
mov %bl, %al
out %al, %dx
// send lba low byte
mov 0ドルx1F3, %dx
movb %es:0x101F1, %al
add 2,ドル %al
out %al, %dx
mov 0ドルx1F4, %dx
mov 0,ドル %al
out %al, %dx
mov 0ドルx1F5, %dx
mov 0,ドル %al
out %al, %dx
// send read command
mov 0ドルx1F7, %dx
mov 0ドルx24, %al
out %al, %dx
jmp _wait_loop
_wait_loop:
mov 0ドルx1F7, %dx
in %dx, %al
testb 0ドルx08, %al
jz _wait_loop
jmp _read
_read:
mov 256,ドル %cx
mov 0ドルx1F0, %dx
rep insw
_sleep:
mov 0ドルx3F6, %dx
in %dx, %al
in %dx, %al
in %dx, %al
in %dx, %al
dec %bl
cmp 0,ドル %bl
je _run_kernel
jmp _wait_loop
_run_kernel:
mov 0ドルx90000, %esi
xor %eax, %eax
mov %eax, %edi
mov %eax, %ebx
mov %eax, %ebp
jmp 0x10200
gdt:
gdt_null:
.long 0x0
.long 0x0
gdt_null_2:
.long 0x0
.long 0x0
gdt_code:
.word 0xFFFF
.word 0x0
.byte 0x0
.byte 0x9A
.byte 0xCF
.byte 0x0
gdt_data:
.word 0xFFFF
.word 0x0
.byte 0x0
.byte 0x92
.byte 0xCF
.byte 0x0
gdt_end:
gdt_desc:
.word gdt_end - gdt - 1
.long gdt
cmd_line:
.asciz "root=/dev/zero console=ttyS0"
.byte 0
cmd_line_size = . - cmd_line
.org 510
.word 0xAA55
Code: Select all
0x102bc: rep stos %ax,%es:(%edi)
For now, I'm not using any initramfs for simplicity, so my expectation is to have a kernel panic at the end.
Any help regarding this. Many thanks in advance
- MichaelPetch
- Member
Member - Posts: 848
- Joined: Fri Aug 26, 2016 1:41 pm
- Libera.chat IRC: mpetch
Re: Single stage bootloader failing to load the linux kernel
Post by MichaelPetch »
From the QEMU log it looks like you got IRQ14 (hard disk disk interrupt). v=76 is the interrupt number it came in on. BIOSes generally default to mapping the Slave PIC to 0x70 [IRQ 8] through 0x77 [IRQ 15]. It is clear from the IDT at that point is likely a real mode IVT as seen from: IDT= 00000000 000003ff. It appears while in protected mode a hard disk interrupt occurred when there was no protected mode IDT set up. The end result was the GPF.
- Octocontrabass
- Member
Member - Posts: 5968
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Single stage bootloader failing to load the linux kernel
Post by Octocontrabass »
[引用]
This looks like it's supposed to be 16-bit code, but the CPU is in 32-bit mode. Are you sure you've jumped to the correct entry point address? Are you sure Linux expects the CPU to be in 32-bit mode when you jump to that address?
Re: Single stage bootloader failing to load the linux kernel
Hello all,
I have finally resolved the issues that I faced for building a single stage bootloader to load a recent linux kernel. But first I want to highlight that I have mixed some concepts in my earlier answers. I'm using qemu for testing and qemu is emulating a legacy bios, for this hardware configuration we don't need to load any boot param struct, we have only to load the boot sector, the setup sectors, fill in the kernel header with the appropriate values and load the protected kernel then jump to the offset 0x200 of the setup sectors, the CPU must remain in the real mode. While for new bioses, it is required to have boot param struct and prepare the CPU to be in a protected mode or long mode depending on the kernel ( whether it is 32 bit kernel or 64 bit one ).
So, the first approach I proposed which had the triple fault error was indeed the correct one, however the code itself had several issues that was introduced due to mainly debugging, then it became tricky to spot these errors:
- a wrong line at start of _start label: mov %cx,%ax ( I don't know from where it came from )
- the 16 bit segments were not totally correct, they allowed to jump back to real mode, but the limit should be 0xFFFF instead of 0xFFFFFFFF and the granularity bit should be set to 0. ( this was not a stopper in itself )
- in this approach the instrcution that was failing was mov %eax, %ds, in the first lines of the protected kernel, it will load a new gdt, for my case the gdt loaded had a 0x0 as a size, therefore it was failing. When debugging I found that the size of gdt should be loaded as part of the protected kernel (the address is 0xe2d000 when loading the protected kernel at 0x100000). Well the issue was that when loading the protected mode kernel I used %bl instead of %bx to verify the number of sectors loaded ( bx contain the exact number of sys sectors ) so in my case I was just loading the number of %bl ( low byte of sector count ), the rest of the kernel were simply zeros.
It was a quite silly error especially that I know what I'm doing when I was loading the kernel and the choise to use ATA PIO with 48bit LBA was behind the reason to jump temporarily to protected mode. However it was a good apportunity to learn more and debug better using gdb and qemu.
I will paste here the correct cpde in case someone wants to try it. For now you will get a kernel panic but it is expected since no ramfs is used, maybe this will be my next step to improve this bootloader.
Thank you all for your help and responsivness, if you have any advice for a beginner like me who is passionate about low level and os, do not hesitate to share your thoughts.
I have finally resolved the issues that I faced for building a single stage bootloader to load a recent linux kernel. But first I want to highlight that I have mixed some concepts in my earlier answers. I'm using qemu for testing and qemu is emulating a legacy bios, for this hardware configuration we don't need to load any boot param struct, we have only to load the boot sector, the setup sectors, fill in the kernel header with the appropriate values and load the protected kernel then jump to the offset 0x200 of the setup sectors, the CPU must remain in the real mode. While for new bioses, it is required to have boot param struct and prepare the CPU to be in a protected mode or long mode depending on the kernel ( whether it is 32 bit kernel or 64 bit one ).
So, the first approach I proposed which had the triple fault error was indeed the correct one, however the code itself had several issues that was introduced due to mainly debugging, then it became tricky to spot these errors:
- a wrong line at start of _start label: mov %cx,%ax ( I don't know from where it came from )
- the 16 bit segments were not totally correct, they allowed to jump back to real mode, but the limit should be 0xFFFF instead of 0xFFFFFFFF and the granularity bit should be set to 0. ( this was not a stopper in itself )
- in this approach the instrcution that was failing was mov %eax, %ds, in the first lines of the protected kernel, it will load a new gdt, for my case the gdt loaded had a 0x0 as a size, therefore it was failing. When debugging I found that the size of gdt should be loaded as part of the protected kernel (the address is 0xe2d000 when loading the protected kernel at 0x100000). Well the issue was that when loading the protected mode kernel I used %bl instead of %bx to verify the number of sectors loaded ( bx contain the exact number of sys sectors ) so in my case I was just loading the number of %bl ( low byte of sector count ), the rest of the kernel were simply zeros.
It was a quite silly error especially that I know what I'm doing when I was loading the kernel and the choise to use ATA PIO with 48bit LBA was behind the reason to jump temporarily to protected mode. However it was a good apportunity to learn more and debug better using gdb and qemu.
I will paste here the correct cpde in case someone wants to try it. For now you will get a kernel panic but it is expected since no ramfs is used, maybe this will be my next step to improve this bootloader.
Code: Select all
.code16
.global _start
.equ BASE_SEG, 0x1000
_start:
cli
xor %ax, %ax
mov %ax, %ds
mov %ax, %ss
mov $BASE_SEG, %ax
mov %ax, %es
xor %bx, %bx
call _load_boot_sector
jmp _switch_pm
_load_boot_sector:
mov 0ドルx02, %ah
mov 1,ドル %al
mov 0,ドル %ch
mov 2,ドル %cl
mov 0,ドル %dh
mov 0ドルx80, %dl
int 0ドルx13
jc _disk_error
jmp _load_setup_sectors
_load_setup_sectors:
mov 0ドルx200, %bx
mov 0ドルx02, %ah
movb %es:0x1F1, %al
mov 0ドルx0, %ch
mov 0ドルx3, %cl
mov 0,ドル %dh
mov 0ドルx80, %dl
int 0ドルx13
jc _disk_error
_init_kernel_header:
mov $BASE_SEG, %ax
mov %ax, %es
movb 0ドルxFF, %es:0x210
// heap
orb 0ドルx80, %es:0x211
// heap end_ptr
movw 0ドルxDE00, %es:0x224
// initramfs
movw 0ドルx0, %es:0x218
mov 0ドルx0, %es:0x21c
// cmd_line_ptr
movl 0ドルx1E000, %es:0x228
_copy_cmd_line:
cld
mov $cmd_line_size, %cx
mov $cmd_line, %si
mov 0ドルxE000, %di
rep movsb
ret
_disk_error:
call _print_error
jmp .
_print_error:
mov 0ドルx0E, %ah
mov $'E', %al
mov 0,ドル %bh
mov 0,ドル %bl
int 0ドルx10
ret
_switch_pm:
lgdt gdt_desc
mov %cr0, %eax
or 0ドルx1, %eax
mov %eax, %cr0
mov 2,ドル %al
out %al, 0ドルx92
ljmp 0ドルx8, $_start_32
.code32
_start_32:
mov 0ドルx10, %ax
mov %ax, %ds
mov %ax, %ss
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov 0ドルx90000, %esp
_load_lba:
// loop control var
mov %es:0x101F4, %edx
shr 5,ドル %edx
mov %dx, %bx
// address of the dest buffer in memory
mov 0ドルx100000, %edi
// init
// send sector count high byte
mov 0ドルx1F2, %dx
mov %bh, %al
out %al, %dx
// send lba high byte
mov 0ドルx1F3, %dx
mov 0,ドル %al
out %al, %dx
mov 0ドルx1F4, %dx
mov 0,ドル %al
out %al, %dx
mov 0ドルx1F5, %dx
mov 0,ドル %al
out %al, %dx
// send sector count low byte
mov 0ドルx1F2, %dx
mov %bl, %al
out %al, %dx
// send lba low byte
mov 0ドルx1F3, %dx
movb %es:0x101F1, %al
add 2,ドル %al
out %al, %dx
mov 0ドルx1F4, %dx
mov 0,ドル %al
out %al, %dx
mov 0ドルx1F5, %dx
mov 0,ドル %al
out %al, %dx
//
mov 0ドルx1F6, %dx
mov 0ドルx40, %al
out %al, %dx
// send read command
mov 0ドルx1F7, %dx
mov 0ドルx24, %al
out %al, %dx
jmp _wait_loop
_wait_loop:
mov 0ドルx1F7, %dx
in %dx, %al
testb 0ドルx08, %al
jz _wait_loop
jmp _read
_read:
mov 256,ドル %cx
mov 0ドルx1F0, %dx
rep insw
_sleep:
mov 0ドルx3F6, %dx
in %dx, %al
in %dx, %al
in %dx, %al
in %dx, %al
dec %bx
cmp 0,ドル %bx
je _reset_16
jmp _wait_loop
// use a 16 bit data/code segment to go back to the real mode
_reset_16:
cli
mov 0ドルx20, %eax
mov %eax, %ds
mov %eax, %ss
mov %eax, %es
mov %eax, %fs
mov %eax, %gs
ljmp 0ドルx18, $_switch_rm
.code16
_switch_rm:
mov %cr0, %eax
and $~1, %eax
mov %eax, %cr0
ljmp 0ドルx0000, $_start_16
_start_16:
// reload the segments with the base seg value: 0x1000
cli
mov $BASE_SEG, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %ss
mov %ax, %gs
mov %ax, %fs
mov 0ドルxE000, %esp
ljmp 0ドルx1020, 0ドルx0
gdt:
gdt_null:
.long 0x0
.long 0x0
gdt_code:
.word 0xFFFF
.word 0x0
.byte 0x0
.byte 0x9A
.byte 0xCF
.byte 0x0
gdt_data:
.word 0xFFFF
.word 0x0
.byte 0x0
.byte 0x92
.byte 0xCF
.byte 0x0
gdt_16_code:
.word 0xFFFF
.word 0x0
.byte 0x0
.byte 0x9b
.byte 0x0
.byte 0x0
gdt_16_data:
.word 0xFFFF
.word 0x0
.byte 0x0
.byte 0x93
.byte 0x0
.byte 0x0
gdt_end:
gdt_desc:
.word gdt_end - gdt - 1
.long gdt
cmd_line:
.asciz "root=/dev/zero console=tty0 nokaslr"
.byte 0
cmd_line_size = . - cmd_line
.org 510
.word 0xAA55
Thank you all for your help and responsivness, if you have any advice for a beginner like me who is passionate about low level and os, do not hesitate to share your thoughts.