From c1afe927cb2f26d564f3232d57729909d06803f5 Mon Sep 17 00:00:00 2001 From: Mathieu Maret Date: Fri, 20 Jul 2018 16:41:32 +0200 Subject: [PATCH] Make kernel multiboot compatible --- Makefile | 4 +- boot.asm | 90 ++++++++++++++++++++++++++++++ boot.s | 109 ++++++++++++++++++++++++++++++++++++ linker.ld | 37 +++++++++---- mbr.asm | 161 ------------------------------------------------------ 5 files changed, 228 insertions(+), 173 deletions(-) create mode 100644 boot.asm create mode 100644 boot.s delete mode 100644 mbr.asm diff --git a/Makefile b/Makefile index 87de96c..ae40b56 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ cobj=$(csrc:%.c=%.o) deps = $(csrc:%.c=%.d) kernel:$(asmobj) $(cobj) linker.ld - $(CXX) $(LDFLAGS) $(cobj) $(asmobj) -o $@ -T linker.ld + $(CC) -m32 -ffreestanding -nostdlib $(cobj) $(asmobj) -o $@ -T linker.ld fd.img: kernel dd if=/dev/zero of=$@ bs=512 count=2880 @@ -33,7 +33,7 @@ core/irq_handler.o:core/irq_handler.c $(AS) $(ASFLAGS) -o $@ $< test:kernel - qemu-system-x86_64 -fda $< + qemu-system-x86_64 -kernel $< clean: $(RM) kernel $(asmobj) $(cobj) $(deps) diff --git a/boot.asm b/boot.asm new file mode 100644 index 0000000..7c4c794 --- /dev/null +++ b/boot.asm @@ -0,0 +1,90 @@ +; Declare constants for the multiboot header. +MBALIGN equ 1 << 0 ; align loaded modules on page boundaries +MEMINFO equ 1 << 1 ; provide memory map +FLAGS equ MBALIGN | MEMINFO ; this is the Multiboot 'flag' field +MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header +CHECKSUM equ -(MAGIC + FLAGS) ; checksum of above, to prove we are multiboot + +; Declare a multiboot header that marks the program as a kernel. These are magic +; values that are documented in the multiboot standard. The bootloader will +; search for this signature in the first 8 KiB of the kernel file, aligned at a +; 32-bit boundary. The signature is in its own section so the header can be +; forced to be within the first 8 KiB of the kernel file. +section .multiboot +align 4 + dd MAGIC + dd FLAGS + dd CHECKSUM + +; The multiboot standard does not define the value of the stack pointer register +; (esp) and it is up to the kernel to provide a stack. This allocates room for a +; small stack by creating a symbol at the bottom of it, then allocating 16384 +; bytes for it, and finally creating a symbol at the top. The stack grows +; downwards on x86. The stack is in its own section so it can be marked nobits, +; which means the kernel file is smaller because it does not contain an +; uninitialized stack. The stack on x86 must be 16-byte aligned according to the +; System V ABI standard and de-facto extensions. The compiler will assume the +; stack is properly aligned and failure to align the stack will result in +; undefined behavior. +section .bss +align 16 +stack_bottom: +resb 16384 ; 16 KiB +stack_top: + +; The linker script specifies _start as the entry point to the kernel and the +; bootloader will jump to this position once the kernel has been loaded. It +; doesn't make sense to return from this function as the bootloader is gone. +; Declare _start as a function symbol with the given symbol size. +section .text +global _start:function (_start.end - _start) +_start: + ; The bootloader has loaded us into 32-bit protected mode on a x86 + ; machine. Interrupts are disabled. Paging is disabled. The processor + ; state is as defined in the multiboot standard. The kernel has full + ; control of the CPU. The kernel can only make use of hardware features + ; and any code it provides as part of itself. There's no printf + ; function, unless the kernel provides its own header and a + ; printf implementation. There are no security restrictions, no + ; safeguards, no debugging mechanisms, only what the kernel provides + ; itself. It has absolute and complete power over the + ; machine. + + ; To set up a stack, we set the esp register to point to the top of our + ; stack (as it grows downwards on x86 systems). This is necessarily done + ; in assembly as languages such as C cannot function without a stack. + mov esp, stack_top + + ; This is a good place to initialize crucial processor state before the + ; high-level kernel is entered. It's best to minimize the early + ; environment where crucial features are offline. Note that the + ; processor is not fully initialized yet: Features such as floating + ; point instructions and instruction set extensions are not initialized + ; yet. The GDT should be loaded here. Paging should be enabled here. + ; C++ features such as global constructors and exceptions will require + ; runtime support to work as well. + + ; Enter the high-level kernel. The ABI requires the stack is 16-byte + ; aligned at the time of the call instruction (which afterwards pushes + ; the return pointer of size 4 bytes). The stack was originally 16-byte + ; aligned above and we've since pushed a multiple of 16 bytes to the + ; stack since (pushed 0 bytes so far) and the alignment is thus + ; preserved and the call is well defined. + ; note, that if you are building on Windows, C functions may have "_" prefix in assembly: _kernel_main + extern kmain + call kmain + + ; If the system has nothing more to do, put the computer into an + ; infinite loop. To do that: + ; 1) Disable interrupts with cli (clear interrupt enable in eflags). + ; They are already disabled by the bootloader, so this is not needed. + ; Mind that you might later enable interrupts and return from + ; kernel_main (which is sort of nonsensical to do). + ; 2) Wait for the next interrupt to arrive with hlt (halt instruction). + ; Since they are disabled, this will lock up the computer. + ; 3) Jump to the hlt instruction if it ever wakes up due to a + ; non-maskable interrupt occurring or due to system management mode. + cli +.hang: hlt + jmp .hang +.end: diff --git a/boot.s b/boot.s new file mode 100644 index 0000000..8aa2a07 --- /dev/null +++ b/boot.s @@ -0,0 +1,109 @@ +/* Declare constants for the multiboot header. */ +.set ALIGN, 1<<0 /* align loaded modules on page boundaries */ +.set MEMINFO, 1<<1 /* provide memory map */ +.set FLAGS, ALIGN | MEMINFO /* this is the Multiboot 'flag' field */ +.set MAGIC, 0x1BADB002 /* 'magic number' lets bootloader find the header */ +.set CHECKSUM, -(MAGIC + FLAGS) /* checksum of above, to prove we are multiboot */ + +/* +Declare a multiboot header that marks the program as a kernel. These are magic +values that are documented in the multiboot standard. The bootloader will +search for this signature in the first 8 KiB of the kernel file, aligned at a +32-bit boundary. The signature is in its own section so the header can be +forced to be within the first 8 KiB of the kernel file. +*/ +.section .multiboot +.align 4 +.long MAGIC +.long FLAGS +.long CHECKSUM + +/* +The multiboot standard does not define the value of the stack pointer register +(esp) and it is up to the kernel to provide a stack. This allocates room for a +small stack by creating a symbol at the bottom of it, then allocating 16384 +bytes for it, and finally creating a symbol at the top. The stack grows +downwards on x86. The stack is in its own section so it can be marked nobits, +which means the kernel file is smaller because it does not contain an +uninitialized stack. The stack on x86 must be 16-byte aligned according to the +System V ABI standard and de-facto extensions. The compiler will assume the +stack is properly aligned and failure to align the stack will result in +undefined behavior. +*/ +.section .bss +.align 16 +stack_bottom: +.skip 16384 # 16 KiB +stack_top: + +/* +The linker script specifies _start as the entry point to the kernel and the +bootloader will jump to this position once the kernel has been loaded. It +doesn't make sense to return from this function as the bootloader is gone. +*/ +.section .text +.global _start +.type _start, @function +_start: + /* + The bootloader has loaded us into 32-bit protected mode on a x86 + machine. Interrupts are disabled. Paging is disabled. The processor + state is as defined in the multiboot standard. The kernel has full + control of the CPU. The kernel can only make use of hardware features + and any code it provides as part of itself. There's no printf + function, unless the kernel provides its own header and a + printf implementation. There are no security restrictions, no + safeguards, no debugging mechanisms, only what the kernel provides + itself. It has absolute and complete power over the + machine. + */ + + /* + To set up a stack, we set the esp register to point to the top of our + stack (as it grows downwards on x86 systems). This is necessarily done + in assembly as languages such as C cannot function without a stack. + */ + mov $stack_top, %esp + + /* + This is a good place to initialize crucial processor state before the + high-level kernel is entered. It's best to minimize the early + environment where crucial features are offline. Note that the + processor is not fully initialized yet: Features such as floating + point instructions and instruction set extensions are not initialized + yet. The GDT should be loaded here. Paging should be enabled here. + C++ features such as global constructors and exceptions will require + runtime support to work as well. + */ + + /* + Enter the high-level kernel. The ABI requires the stack is 16-byte + aligned at the time of the call instruction (which afterwards pushes + the return pointer of size 4 bytes). The stack was originally 16-byte + aligned above and we've since pushed a multiple of 16 bytes to the + stack since (pushed 0 bytes so far) and the alignment is thus + preserved and the call is well defined. + */ + call kmain + + /* + If the system has nothing more to do, put the computer into an + infinite loop. To do that: + 1) Disable interrupts with cli (clear interrupt enable in eflags). + They are already disabled by the bootloader, so this is not needed. + Mind that you might later enable interrupts and return from + kernel_main (which is sort of nonsensical to do). + 2) Wait for the next interrupt to arrive with hlt (halt instruction). + Since they are disabled, this will lock up the computer. + 3) Jump to the hlt instruction if it ever wakes up due to a + non-maskable interrupt occurring or due to system management mode. + */ + cli +1: hlt + jmp 1b + +/* +Set the size of the _start symbol to the current location '.' minus its start. +This is useful when debugging or when you implement call tracing. +*/ +.size _start, . - _start diff --git a/linker.ld b/linker.ld index 19d67b2..88b0e01 100644 --- a/linker.ld +++ b/linker.ld @@ -1,26 +1,43 @@ -ENTRY(boot) -OUTPUT_FORMAT("binary") -SECTIONS { - . = 0x7c00; - .text : +/* The bootloader will look at this image and start execution at the symbol + designated as the entry point. */ +ENTRY(_start) + +/* Tell where the various sections of the object files will be put in the final + kernel image. */ +SECTIONS +{ + /* Begin putting sections at 1 MiB, a conventional place for kernels to be + loaded at by the bootloader. */ + . = 1M; + + /* First put the multiboot header, as it is required to be put very early + early in the image or the bootloader won't recognize the file format. + Next we'll put the .text section. */ + .text BLOCK(4K) : ALIGN(4K) { - *(.boot) + *(.multiboot) *(.text) } - .rodata : + /* Read-only data. */ + .rodata BLOCK(4K) : ALIGN(4K) { *(.rodata) } - .data : + /* Read-write data (initialized) */ + .data BLOCK(4K) : ALIGN(4K) { *(.data) } - .bss : + /* Read-write data (uninitialized) and stack */ + .bss BLOCK(4K) : ALIGN(4K) { + *(COMMON) *(.bss) } -} + /* The compiler may produce other sections, by default it will put them in + a segment with the same name. Simply add stuff here as needed. */ +} diff --git a/mbr.asm b/mbr.asm deleted file mode 100644 index 8120b83..0000000 --- a/mbr.asm +++ /dev/null @@ -1,161 +0,0 @@ -section .boot -bits 16 -global boot -boot: - jmp main - -display_enable: - push bp - mov bp, sp - mov ah, 0h ; 00h Set Video Mode - mov al, 07h ; Txt, monochrome, 80x25 - - int 10h - - mov sp, bp - pop bp - ret - -print: - push bp - mov bp, sp - mov si, [bp + 4]; put first function arg in si. sp is stask pointer -.loop: - lodsb ; load si content into al then inc si - cmp al, 0; - je .end - - mov ah, 0eh - mov bx, 0 - int 10h - - jmp .loop - -.end: - mov sp, bp - pop bp - ret - -println: - push bp - mov bp, sp - push word [bp + 4] - call print - add sp, 2 - - mov ah, 03h ; read cursor position - int 10h ; row number in dh. Col in dl - - inc dh ; goto next line - mov dl, 0 - - mov ah, 02h ; Set Cursor Position - int 10h - mov sp, bp - pop bp - ret - -hello db 'Booting matOs', 0 - -main: - sti ; enable virtual interupts - mov [disk],dl ; save disk used to boot by bios - - call display_enable - - push hello - call println - add sp, 2 - -; Switch in 32bits Protected mode - - ; Activate A20 http://wiki.osdev.org/A20_Line to be able to access more than 1Mb memory - mov ah, 0h - mov ax, 0x2401 - int 0x15 - - ; Change video mode to display VGA - mov ax, 0x3 - int 0x10 - - ; http://www.ctyme.com/intr/rb-0607.htm - ; Bios read first 512 bytes, read next disk sector - mov ah, 0x2 ;read sectors - mov al, 15 ;sectors to read - mov ch, 0 ;cylinder idx - mov dh, 0 ;head idx - mov cl, 2 ;sector idx - mov dl, [disk] ;disk idx - mov bx, copy_target;target pointer - int 0x13 - - cli ; disable interruption when setting GDT - - ; switch in 32 bits - lgdt [gdt_pointer] ; switch in 32bits here - mov eax, cr0 - or eax,0x1; set the protected mode bit on special CPU reg cr0 - mov cr0, eax - jmp CODE_SEG:boot2 ; In protected mode we need to add the segment selector - -; GDT table desciption could be found http://wiki.osdev.org/Global_Descriptor_Table -; here we define the 3 64bits segment needed: null segment, code segment and data segment -gdt_start: ;null segment - dq 0x0 -gdt_code: ;code segment - dw 0xFFFF ; limit [0:15] - dw 0x0 ; base [0:15] - db 0x0 ; base [16:23] - db 10011010b ; access byte: Present(1)| Priv(2) 0 ->kernel 3->userspace | 1 | Executable(1) | Direction/Conformity (1) | RW(1) | Accessed(1) - db 11001111b ; Granularity(1) | Size (1) 0-> 16bit mode 1->32protected mode | 0 | 0 | Limit [16:19] - db 0x0 ; base [24:31] -gdt_data: - dw 0xFFFF - dw 0x0 - db 0x0 - db 10010010b - db 11001111b - db 0x0 -gdt_end: -gdt_pointer: - dw gdt_end - gdt_start - dd gdt_start -disk: - db 0x0 -CODE_SEG equ gdt_code - gdt_start -DATA_SEG equ gdt_data - gdt_start - -times 510 - ($-$$) db 0 -dw 0xaa55 -copy_target: -bits 32 -boot2: - mov ax, DATA_SEG ; set all segments to point to DATA_SEG https://en.wikipedia.org/wiki/X86_memory_segmentation - mov ds, ax ; Data segment - mov es, ax ; Extra Segment (for string operation) - mov fs, ax ; No Specific use - mov gs, ax ; No Specific use - mov ss, ax ; stack segment - mov esi,hello32 - mov ebx,0xb8000 ; Cannot use BIOS anymore, use VGA Text buffer instead - -.loop32: - lodsb - or al,al - jz halt - or eax,0x0100 ; blue bg - mov word [ebx], ax - add ebx,2 - jmp .loop32 -halt: - mov esp,kernel_stack_top - extern kmain - call kmain - cli - hlt -hello32: db "Hello 32 bits world!",0 -section .bss -align 4 -kernel_stack_bottom: equ $ - resb 16384 ; 16 KB -kernel_stack_top: