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 Answers
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.

- 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
-