0

I'm developing a homebrew os and I can't figure out how to switch from my homebrew bootloader to my homebrew kernel. I do not know how it works  I know it starts with memory, but I don't know what number it starts with I've been told that I can just look at the minix source, but when I parse it from the iso, it doesn't tell me anything at all, all it tells me is the configuration of the linux directory. I've been trying to figure it out for a week, but nothing has come up. I'm sorry if I'm not trying hard enough. And since there didn't seem to be any similar questions, I thought I'd ask you a question

  • Well, you have your bootloader read from disk the sectors where your kernel is stored, and then jump to whatever location in memory corresponds to your kernel's entry point. But your question is too vague to be able to tell which part of that is your problem. – Nate Eldredge Apr 12 '21 at 00:46
  • his is where you change from the bootloader to the ke I don't know how to describe it –  Apr 12 '21 at 01:48

1 Answers1

1

I'll show you a well commented minimal example of jumping to a C++ kernel I wrote so maybe you can go from there and write your own. I work on Linux Ubuntu 20.

In your home directory create a folder named OS and create a file named kernel.cpp in that folder. Place the following content in that file:

void map_framebuffer(){
    unsigned long* page_directory_table_pointer = (unsigned long*) 0xb018;
    *page_directory_table_pointer = 0x1e00f;
    unsigned long* page_table_pointer = (unsigned long*) 0x1e000;
    /*
    This is the actual address of the framebuffer in QEMU
    for the video mode I set up in boot.asm.
    In this case it is hardcoded. You may want to actually get this from the VBE structure.
    The VBE structure is placed at 0x7e00 in memory by the boot.asm routine.
    */
    unsigned long framebuffer_address = (unsigned long) 0xfd00000f;
    for (unsigned int i = 0; i < 512; i++){
        *page_table_pointer = framebuffer_address;
        framebuffer_address += 0x1000;
        page_table_pointer++;
    }
}

void main(){
    map_framebuffer();
    
    /*
    I made sure that the framebuffer is mapped to adress 0x600000 in the map_framebuffer() function.
    You may want to identity map it instead or map it somewhere else. You'll then have to 
    calculate page table offsets and stuff.
    */
    unsigned char* framebuffer_pointer = (unsigned char*)0x600000;
    //Make a few pixels become white. Each pixel is 3 bytes RGB.
    for (unsigned int i = 0; i < 10000; i++)
        *(framebuffer_pointer + i) = 0xff;
        
    asm volatile(
        "halt:\n\t"
        "hlt\n\t"
        "jmp halt\n\t");
}

/*
Memory map of kernel
7e00 - 8000 -> VBE INFO structure
8000 - 9000 -> paging.bin (booting asm code)
9000 - a000 -> pml4
a000 - b000 -> pdpt
b000 - c000 -> pdt
1b000 - 1c000 -> pt1 (kernel identity mapped pages)
1c000 - 1d000 -> pt2 (kernel identity mapped pages)
1d000 - 1e000 -> pt3 (kernel identity mapped pages)
1e000 - 1f000 -> pt4 (mapped to the framebuffer, only 2MB are mapped to the framebuffer which could take more than this)
30000 - 80000 -> kernel.elf (temporary kernel file)
80000 -> kernel stack (growing downward)
*/

Now create a file named boot.asm in the same directory and write the following content into it:

org 0x7c00

bits 16

;Set the video mode to VBE graphics mode for being able to draw onto the screen
jmp get_vbe_info
error:
mov ah, 0x0e
mov al, 'e'
int 0x10
hlt

get_vbe_info:
mov ax, 0x4f01 ;c0 00 00 00
mov cx, 0x118
xor bx, bx
mov es, bx
mov di, 0x7e00
int 0x10
cmp ax, 0x004f
jne error

set_video_mode:
mov ax, 0x4f02
mov bx, 0x4118
int 0x10
cmp ax, 0x004f
jne error

;Set up a real mode stack
mov ax, 0x00
mov ss, ax
mov sp, 0x7c00

;Load the second stage bootloader at 0x8000
mov ah, 0x00    ;reset drive
int 0x13
xor ax, ax
mov es, ax
mov bx, 0x8000  ;es:bx = 0x0000:0x8000 = 0x8000
mov ah, 0x02    ;read sector function
mov al, 0x09    ;read 9 sectors
mov ch, 0x00    ;cylinder 0
mov dh, 0x00    ;head 0
mov cl, 0x02    ;start from sector 2
int 0x13

;Load the cpp kernel at 0x30000
mov ah, 0x00
int 0x13
mov ax, 0x3000
mov es, ax
xor bx, bx  ;es:bx = 0x3000:0x0000 = 0x3000 * 0x10 + 0x0 = 0x30000
mov ah, 0x02    ;read sector function
mov al, 0x3c    ;read 60 sectors to load the whole file
mov ch, 0x00    ;cylinder 0
mov dh, 0x00    ;head 0
mov cl, 0x0a    ;start from sector 10 (because this is where lies the ELF CPP kernel in the disk.img we created with dd)
int 0x13

xor ax, ax
mov ds, ax

;Set up a protected mode stack
mov ax, 0x10    ;10000b = 10 for segment selector 2 (data)
mov ss, ax
mov sp, 0x7c00

cli

lgdt[gdtr]

;Enable protected mode
mov eax, cr0
or al, 1
mov cr0, eax

jmp 0x08:protectedMode      ;0x08 = 1000b, 1 for segment selector
                            ;0 for gdt not ldt and 00 for privilege
                            
bits 32

protectedMode:

mov ax, 0x10
mov ds, ax

;Jump to 0x8000 (the second stage bootloader)
jmp 0x08:0x8000

gdt_start:
        dq 0x0
gdt_code:
        dw 0xFFFF   ;limit 0-15
        dw 0x0      ;base 0-15
        db 0x0      ;base 16-23 
        db 10011010b    ;pr, privi (2), s, ex, dc, rw, ac 
        db 11001111b    ;gr, sz, limit 16-19
        db 0x0      ;base 24-31
gdt_data:
        dw 0xFFFF
        dw 0x0
        db 0x0
        db 10010010b
        db 11001111b
        db 0x0  
gdtr:
        dw 24
        dd gdt_start

times 510 - ($-$$) db 0
dw 0xAA55

Now create a third file named paging.asm and place the following into it:

org 0x8000

bits 32

;Set up paging
mov eax, 0x00009008
mov cr3, eax

pml4t:
mov dword [0x9000], 0x0000a00f
mov dword [0x9004], 0x0
pdpt:
mov dword [0xa000], 0x0000b00f
mov dword [0xa004], 0x0
pdt:
mov dword [0xb000], 0x0001b00f
mov dword [0xb004], 0x0
mov dword [0xb008], 0x0001c00f
mov dword [0xb00c], 0x0
mov dword [0xb010], 0x0001d00f
mov dword [0xb014], 0x0
pt:
mov edx, 0x3
mov eax, 0x200
mov ebx, 0x0000000f
mov ecx, 0x1b000
next_table:
next_entry:
mov dword [ecx], ebx
add ecx, 0x4
mov dword [ecx], 0x0
add ebx, 0x1000     ;add 4096 to the adress pointed to by ebx (the next physical page)
add ecx, 0x4
sub eax, 0x1
cmp eax, 0x0
jne next_entry
mov eax, 0x200
sub edx, 0x1
cmp edx, 0x0
jne next_table

mov eax, cr4            ;enable PAE-paging
or eax, 1 << 5
mov cr4, eax

mov ecx, 0xC0000080     ;set long mode bit in EFER MSR
rdmsr
or eax, 1 << 8
wrmsr

mov eax, cr0            ;enable paging
or eax, 1 << 31
mov cr0, eax

lgdt[gdtr]          ;load a 64 bit gdt (will be ignored afterwards)

jmp 0x08:longMode

bits 64

longMode:

mov ax, 0x10        ;10000b = 10 for segment selector 2 (data)
mov ss, ax
mov rsp, 0x80000

mov rdi, 0x30000    ;address where elf reading occurs
mov rsi, 0x30000

add rdi, 24         ;program entry
mov rax, [rdi]      ;placed in rax

add rdi, 8          ;program header table position
mov rbx, [rdi]      ;put it in rbx

add rdi, 24         ;move to number of entries in program header
mov cx, [rdi]       ;put it in cx (2 bytes)

mov rdi, 0x30000    ;beginning of file
add rdi, rbx        ;go to program header 0
add rsi, rbx

next_segment:
mov edx, [rdi]      ;put the type of segment in edx (4 bytes)
cmp edx, 0x1        ;determine if it is loadable
jne not_loadable    ;if it isnt jump to not_loadable
add rdi, 0x8        ;else load it
mov r8, [rdi]       ;put the offset in the file where data for this segment resides in r8
add rdi, 0x8        ;go to virtual address of segment
mov r9, [rdi]       ;put it in r9
add rdi, 0x10       ;move to size of segment in file
mov r10, [rdi]      ;put it in r10
add rdi, 0x8        ;move to size of segment in memory
mov r11, [rdi]      ;put it in r11

mov rdi, 0x30000    ;move back to beginning of file
add rdi, r8         ;add segment offset to be at the segment position

next_byte:
mov dl, [rdi]       ;put the byte at rdi in dl
mov [r9], dl        ;move it to virtual address in r9
add r9, 0x1         ;add 1 byte to virtual address
add rdi, 0x1        ;add 1 byte to rdi
sub r10, 0x1        ;substract 1 byte from size of segment in file
sub r11, 0x1        ;substract 1 byte from size of segment in memory
cmp r10, 0x0        ;is segment finished
jne next_byte       ;if not go to next_byte
cmp r11, 0x0        ;if yes tcheck memory size
je no_padding       ;if no padding required jmp to no_padding
padding:
xor dl, dl
mov [r9], dl
add r9, 0x01
sub r11, 0x1
cmp r11, 0x0
jne padding
                    
not_loadable:
no_padding:
sub cx, 0x1
cmp cx, 0x0
je finished
add rsi, 56
mov rdi, rsi
jmp next_segment

finished:

jmp rax ;Jump to the entry point of the ELF CPP file

halt:
hlt
jmp halt

;The 64-bits GDT
gdt_start:
        dw 0xFFFF
    dw 0
    db 0                        
    db 0
    db 1
    db 0
gdt_code:
    dw 0x1111   ;limit 0-15
        dw 0x0      ;base 0-15
        db 0x0      ;base 16-23 
        db 10011010b    ;pr, privi (2), s, ex, dc, rw, ac 
        db 10101111b    ;gr, sz, limit 16-19
        db 0x0      ;base 24-31
gdt_data:
        dw 0x1111
        dw 0x0
        db 0x0
        db 10010010b
        db 00001111b
        db 0x0  
gdtr:
        dw $ - gdt_start - 1
        dq gdt_start

Now open a bash terminal and type the following commands in order (making sure to have NASM, g++ and QEMU installed):

nasm -fbin  OS/boot.asm -oOS/boot.bin

nasm -fbin OS/paging.asm -oOS/paging.bin

g++ -static -ffreestanding -nostdlib -c -m64 OS/kernel.cpp -oOS/kernel.o

ld --entry main --oformat elf64-x86-64 --no-dynamic-linker -static -nostdlib OS/kernel.o -oOS/kernel.elf

dd if=/dev/zero of=OS/disk.img count=100 & dd if=OS/boot.bin of=OS/disk.img conv=notrunc & dd if=OS/paging.bin of=OS/disk.img seek=1 conv=notrunc & dd if=OS/kernel.elf of=OS/disk.img seek=9 conv=notrunc

qemu-system-x86_64 -hda OS/disk.img -s

You can always put all that in a bash file and launch it automatically. The -s in the QEMU command makes the virtual machine listen on port 1234. You can use gdb to debug the kernel. Type in gdb in a bash shell then target remote localhost:1234. You will then be debugging the machine. You can type dump memory result.bin 0x1000 0x2000 to dump the memory of the machine from 0x1000 to 0x2000 (or any address) in the file result.bin. You can use hexdump -C result.bin to look at the memory dump from terminal. The output is little endian so you need to reverse it for the actual values.

In the code above I'm loading the ELF file compiled with g++ to address 0x30000. Then I'm parsing the file and jumping to its entry point. The cpp code maps the framebuffer in virtual memory and makes a few pixels become white. The code is far from perfect but if you understand it in its entirety then you can go a long way.

user123
  • 2,510
  • 2
  • 6
  • 20
  • Thank you for the long source code! I would like to design it based on the source code. Where can I find the memory address and other specifications? –  Apr 12 '21 at 10:01
  • You can find most information on the code on osdev.org. Also the memory addresses I chose are arbitrary but rely on the standard memory map of RAM at boot. You can find this memory map on osdev.org as well. – user123 Apr 12 '21 at 23:09
  • I'll try my best, it's just going to take huge amount of knowledge. –  Apr 14 '21 at 08:48