Hello. I am trying to understand the
Linux Boot Protocol and came across
this github repo. I also came across
this YouTube video showing how to use the linux kernel to develop your own OS. In minute 21:35 you can see how he basically made an init executable that shows the 'TICK!' message every few seconds. What I am basically trying to do is what the guy form the YouTube video made
but using the tiny linux bootloader from the GitHub repository
instead of Grub, but I can't because I get a Guru meditation error in VirtualBox and I wanted to see if you can tell me what I am doing wrong. Keep in mind that I successfully made the 'TICK!' thing with Grub as shown in youtube.
In VirtualBox I have 2 machines: The one I use to develop and the one I use to try the OS. In the first one I have attached 2 hard disks: One with Ubuntu installed (/dev/sda) and another where the new OS goes (/dev/sdb). In the second machine there is only one hard disk attached which is the same I use in the main one with Ubuntu to install the new OS. In other words, /dev/sdb in main machine is /dev/sda in machine with custom OS.
These are the files and commands used in the youtube video, summarized for you:
Attachment:
files.zip [4.97 KiB]
Downloaded 21 times
start.SCode:
.globl _start
.text
_start:
call main
.globl _syscall
_syscall:
movq %rdi, %rax
movq %rsi, %rdi
movq %rdx, %rsi
movq %rcx, %rdx
movq %r8, %r10
movq %r9, %r8
movq 8(%rsp), %r9
syscall
ret
init.cCode:
#include <syscall.h>
#include <fcntl.h>
unsigned long _syscall(int num, void *a0, void *a1, void *a2, void *a3, void *a4, void *a5);
unsigned long _strlen(char *sz) {
int count = 0;
while(*sz++) {
count++;
}
return count;
}
void delay(int ticks) {
for (int i=0; i<ticks; i++) {
//nothing...
}
}
void print_string(char *str) {
_syscall(SYS_write, (void *)1 /*stdout*/, str, (void *)_strlen(str), 0, 0, 0);
}
int main() {
char *msg = "MyOS 0.0.0.1 Initializing...\n";
print_string(msg);
while(1) {
//event loop, for now just tick...
delay(10000000);
print_string("TICK!\n");
}
return 0;
}
grub.cfgCode:
set default=0
set timeout=30
menuentry "MyOS 0.0.0.1" {
linux /boot/vmlinuz root=/dev/sda1 ro
initrd /boot/initrd.img
}
The commands:Code:
# Execute all commands as root
sudo su
# Install GCC
apt install -y gcc
# Clean the contents of the drive
dd if=/dev/zero of=/dev/sdb bs=300000000 count=1
# Format the disk with a single partition that takes up the whole disk
sfdisk --label=dos /dev/sdb <<<', , , *'
# Create the filesystem
mkfs.ext4 /dev/sdb1
mkdir /mnt/myos
mount /dev/sdb1 /mnt/myos
rm -rf /mnt/myos/lost+found
mkdir -pv /mnt/myos/{bin,sbin,etc,lib,lib64,var,dev,proc,sys,run,tmp,boot}
mknod -m 600 /mnt/myos/dev/console c 5 1
mknod -m 666 /mnt/myos/dev/null c 1 3
# Copy the linux kernel and initrd image
cp /boot/vmlinuz-$(uname -r) /mnt/myos/boot/vmlinuz
cp /boot/initrd.img-$(uname -r) /mnt/myos/boot/initrd.img
# Install grub in /dev/sdb
grub-install /dev/sdb --skip-fs-probe --boot-directory=/mnt/myos/boot
# Compile and use the init.c file
gcc -nostdlib -ffreestanding -no-pie init.c start.S -o init
cp init /mnt/myos/sbin/
# Put the grub.cfg file in /myos/boot/grub
cp grub.cfg /mnt/myos/boot/grub/
Below are the files and commands I execute to use the tiny linux bootloader with the TICK! init executable. Apart from removing the GNU GPL License from the files for simplicity, these are the modifications that I did to them:
- config.inc: Changed the cmdLineDef to "root=/dev/sda1 ro" because that's the one used in the grub.cfg file of the youtube video.
- bsect.asm: Didn't change anything
- build.sh: Changed the KERN and RD variables to use my kernel and initrd image with $(uname -r)
Also note that the build.sh script prints in the last line the sector where the first partition should start. You will have to replace 187814 with this value in the sfdisk command.
config.incCode:
%define cmdLineDef "root=/dev/sda1 ro"
;initRdSizeDef - defined in build script automatically
bsect.asmCode:
%define DEBUG
%include "config.inc"
[BITS 16]
org 0x7c00
cli
xor ax, ax
mov ds, ax
mov ss, ax
mov sp, 0x7c00 ; setup stack
; now get into protected move (32bit) as kernel is large and has to be loaded high
mov ax, 0x2401 ; A20 line enable via BIOS
int 0x15
jc err
lgdt [gdt_desc]
mov eax, cr0
or eax, 1
mov cr0, eax
jmp $+2
mov bx, 0x8 ; first descriptor in GDT
mov ds, bx
mov es, bx
mov gs, bx
and al, 0xFE ; back to real mode
mov cr0, eax
xor ax,ax ; restore segment values - now limits are removed but seg regs still work as normal
mov ds, ax
mov gs, ax
mov ax, 0x1000 ; segment for kernel load (mem off 0x10000)
mov es, ax
sti
; now in UNREAL mode
mov ax, 1 ; one sector
xor bx,bx ; offset
mov cx, 0x1000 ; seg
call hddread
read_kernel_setup:
mov al, [es:0x1f1] ; no of sectors
cmp ax, 0
jne read_kernel_setup.next
mov ax, 4 ; default is 4
.next:
; ax = count
mov bx, 512 ; next offset
mov cx, 0x1000 ; segment
call hddread
cmp word [es:0x206], 0x204
jb err
test byte [es:0x211], 1
jz err
mov byte [es:0x210], 0xe1 ;loader type
mov byte [es:0x211], 0x81 ;heap use? !! SET Bit5 to Make Kern Quiet
mov word [es:0x224], 0xde00 ;head_end_ptr
mov byte [es:0x227], 0x01 ;ext_loader_type / bootloader id
mov dword [es:0x228], 0x1e000 ;cmd line ptr
; copy cmd line
mov si, cmdLine
mov di, 0xe000
mov cx, cmdLineLen
rep movsb ; copies from DS:si to ES:di (0x1e000)
; modern kernels are bzImage ones (despite name on disk and so
; the protected mode part must be loaded at 0x100000
; load 127 sectors at a time to 0x2000, then copy to 0x100000
;load_kernel
mov edx, [es:0x1f4] ; bytes to load
shl edx, 4
call loader
;load initrd
mov eax, 0x7fab000; this is the address qemu loads it at
mov [highmove_addr],eax ; end of kernel and initrd load address
;mov eax, [highmove_addr] ; end of kernel and initrd load address
;add eax, 4096
;and eax, 0xfffff000
;mov [highmove_addr],eax ; end of kernel and initrd load address
mov [es:0x218], eax
mov edx, [initRdSize] ; ramdisk size in bytes
mov [es:0x21c], edx ; ramdisk size into kernel header
call loader
kernel_start:
cli
mov ax, 0x1000
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov sp, 0xe000
jmp 0x1020:0
jmp $
; ================= functions ====================
;length in bytes into edx
; uses hddread [hddLBA] and highmove [highmove_addr] vars
;clobbers 0x2000 segment
loader:
.loop:
cmp edx, 127*512
jl loader.part_2
jz loader.finish
mov ax, 127 ;count
xor bx, bx ; offset
mov cx, 0x2000 ; seg
push edx
call hddread
call highmove
pop edx
sub edx, 127*512
jmp loader.loop
.part_2: ; load less than 127*512 sectors
shr edx, 9 ; divide by 512
inc edx ; increase by one to get final sector if not multiple - otherwise just load junk - doesn't matter
mov ax, dx
xor bx,bx
mov cx, 0x2000
call hddread
call highmove
.finish:
ret
highmove_addr dd 0x100000
; source = 0x2000
; count = 127*512 fixed, doesn't if matter we copy junk at end
; don't think we can use rep movsb here as it wont use EDI/ESI in unreal mode
highmove:
mov esi, 0x20000
mov edi, [highmove_addr]
mov edx, 512*127
mov ecx, 0 ; pointer
.loop:
mov eax, [ds:esi]
mov [ds:edi], eax
add esi, 4
add edi, 4
sub edx, 4
jnz highmove.loop
mov [highmove_addr], edi
ret
err:
%ifdef DEBUG
mov si, errStr
call print
%endif
jmp $
%ifdef DEBUG
; si = source str
print:
lodsb
and al, al
jz print.end
mov ah, 0xe
mov bx, 7
int 0x10
jmp print
print.end:
ret
%endif
hddread:
push eax
mov [dap.count], ax
mov [dap.offset], bx
mov [dap.segment], cx
mov edx, dword [hddLBA]
mov dword [dap.lba], edx
and eax, 0xffff
add edx, eax ; advance lba pointer
mov [hddLBA], edx
mov ah, 0x42
mov si, dap
mov dl, 0x80 ; first hdd
int 0x13
jc err
pop eax
ret
dap:
db 0x10 ; size
db 0 ; unused
.count:
dw 0 ; num sectors
.offset:
dw 0 ;dest offset
.segment:
dw 0 ;dest segment
.lba:
dd 0 ; lba low bits
dd 0 ; lba high bits
;descriptor
gdt_desc:
dw gdt_end - gdt - 1
dd gdt
; access byte: [present, priv[2] (0=highest), 1, Execbit, Direction=0, rw=1, accessed=0]
; flags: Granuality (0=limitinbytes, 1=limitin4kbs), Sz= [0=16bit, 1=32bit], 0, 0
gdt:
dq 0 ; first entry 0
;flat data segment
dw 0FFFFh ; limit[0:15] (aka 4gb)
dw 0 ; base[0:15]
db 0 ; base[16:23]
db 10010010b ; access byte
db 11001111b ; [7..4]= flags [3..0] = limit[16:19]
db 0 ; base[24:31]
gdt_end:
%ifdef DEBUG
errStr db 'err!!',0
%endif
; config options
cmdLine db cmdLineDef,0
cmdLineLen equ $-cmdLine
initRdSize dd initRdSizeDef ; from config.inc
hddLBA dd 1 ; start address for kernel - subsequent calls are sequential
;boot sector magic
times 510-($-$$) db 0
dw 0xaa55
; real mode print code
; mov si, strhw
; mov eax, 0xb8000
; mov ch, 0x1F ; white on blue
;loop:
; mov cl, [si]
; mov word [ds:eax], cx
; inc si
; add eax, 2
; cmp [si], byte 0
; jnz loop
build.shCode:
INPUT="bsect.asm"
OUTPUT="disk"
KERN="/boot/vmlinuz-$(uname -r)"
RD="/boot/initrd.img-$(uname -r)"
#size of kern + ramdisk
K_SZ=`stat -c %s $KERN`
R_SZ=`stat -c %s $RD`
#padding to make it up to a sector
K_PAD=$((512 - $K_SZ % 512))
R_PAD=$((512 - $R_SZ % 512))
nasm -o $OUTPUT -D initRdSizeDef=$R_SZ $INPUT
cat $KERN >> $OUTPUT
if [[ $K_PAD -lt 512 ]]; then
dd if=/dev/zero bs=1 count=$K_PAD >> $OUTPUT
fi
cat $RD >> $OUTPUT
if [[ $R_PAD -lt 512 ]]; then
dd if=/dev/zero bs=1 count=$R_PAD >> $OUTPUT
fi
TOTAL=`stat -c %s $OUTPUT`
echo "concatenated bootloader, kernel and initrd into ::> $OUTPUT"
echo "Note, your first partition must start after sector $(($TOTAL / 512))"
The commands:Code:
# Execute all commands as root
sudo su
# Install GCC & NASM
apt install -y gcc nasm
# Build the bootloader
./build.sh
# Clean the contents of the drive
dd if=/dev/zero of=/dev/sdb bs=300000000 count=1
# Format the disk with a single partition that takes up the whole disk starting as where the build.sh script said
sfdisk --label=dos /dev/sdb <<<'187814, , , *'
# Create the filesystem
mkfs.ext4 /dev/sdb1
mkdir /mnt/myos
mount /dev/sdb1 /mnt/myos
rm -rf /mnt/myos/lost+found
mkdir -pv /mnt/myos/{bin,sbin,etc,lib,lib64,var,dev,proc,sys,run,tmp,boot}
mknod -m 600 /mnt/myos/dev/console c 5 1
mknod -m 666 /mnt/myos/dev/null c 1 3
# Compile and use the init.c file
gcc -nostdlib -ffreestanding -no-pie init.c start.S -o init
cp init /mnt/myos/sbin/
# Replace the MBR in the generated disk file
dd if=/dev/sdb of=disk bs=1 count=$((512-446)) skip=446 seek=446 conv=notrunc
# Install the bootloader in /dev/sdb
dd if=disk of=/dev/sdb conv=notrunc