PrettyOS für GRUB kompilieren/anpassen
-
Hallo,
habe, wie gestern angesprochen, PrettyOS Kernel zum Booten mit GRUB kompiliert/angepasst (unter Linux, mit Hilfe der folgenden Quelle http://lowlevel.brainsware.org/wiki/index.php/C-Kernel_mit_GRUB).
Anbei Makefile makefile_linux:SOURCES = kernel.asm $(filter-out kernel.asm ,$(wildcard *.asm *.c)) OBJECTS = $(addsuffix .o,$(basename $(SOURCES))) ASFLAGSOBJ= -O32 -f elf CFLAGS = -std=c99 CFLAGS += -march=i386 CFLAGS += -mtune=i386 CFLAGS += -Werror CFLAGS += -Wall CFLAGS += -O2 CFLAGS += -fomit-frame-pointer CFLAGS += -ffreestanding CFLAGS += -fleading-underscore CFLAGS += -nostdlib CFLAGS += -nostdinc CFLAGS += -fno-builtin CFLAGS += -fno-stack-protector CFLAGS += -Iinclude CC = gcc LDFLAGS= -T kernel.ld -Map kernel.map -nostdinc LDFLAGS_GRUB = -T kernelgrub.ld -Map kernelgrub.map -nostdinc LD = ld all: ckernel.sys ckernelgrub.sys @echo No errors for $@. %.o: %.asm nasm $(ASFLAGSOBJ) $< -o $@ @echo No errors for $@. ckernel.sys: $(OBJECTS) $(LD) $(LDFLAGS) $+ -o $@ @echo No errors for $@. ckernelgrub.sys: $(OBJECTS) $(LD) $(LDFLAGS_GRUB) $+ -o $@ @echo No errors for $@. data.asm: initrd.dat @echo No errors for $@. clean: rm -fv *.o @echo No errors for $@.
Neu sind dazugekommen: LDFLAGS_GRUB und ckernelgrub.sys.
Für den Linker gibt es einen zweiten Linkerscript kernelgrub.ld:ENTRY (KernelStartFromGRUB) OUTPUT_FORMAT(elf32-i386) OUTPUT_ARCH(i386:i386) SECTIONS { . = 0x00100000; .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } .bss : { __bss_start = .; _sbss = .; *(COMMON) *(.bss) _ebss = .; } __end = .; }
Die kernel.asm Datei musste wie folgt angepasst werden:
[Bits 32] global KernelStart global KernelStartFromGRUB extern __bss_start extern __end extern _main ; entry point in ckernel.c ; Flags for GRUB header GRUB_FLAGS equ 0 ; Magic number for GRUB header GRUB_MAGIC_NUMBER equ 0x1BADB002 ; Checksum for GRUB header GRUB_HEADER_CHECKSUM equ -(GRUB_MAGIC_NUMBER + GRUB_FLAGS) section .text ; ld needs that for coff format align 4 MultiBootHeader: ; This is a "multiboot" header for GRUB, this structure must be first (?) dd GRUB_MAGIC_NUMBER dd GRUB_FLAGS dd GRUB_HEADER_CHECKSUM ; This is an entry point for native PrettyOS bootloader KernelStart: mov ax, 0x10 mov ds, ax ; data descriptor --> data, stack and extra segment mov ss, ax mov es, ax xor eax, eax ; null desriptor --> FS and GS mov fs, ax mov gs, ax ; set bss section to zero mov edi, __bss_start mov ecx, __end sub ecx, __bss_start mov al, 0 rep stosb ; repeats instruction decrementing ECX until zero ; and stores value from AL incrementing ES:EDI mov esp, 0x190000 ; set stack below 2 MB limit call _main ; ->-> C-Kernel InfiniteLoop: ; Perform infinite loop in case the kernel returns from main cli ; Disable interrupts and try to halt the CPU hlt jmp InfiniteLoop ; This is an entry point for GRUB KernelStartFromGRUB: mov esp, 0x00200000 push eax ; EAX contains "multiboot" magic number (?) push ebx ; EBX contains address of the "multiboot" structure (?) call _main jmp InfiniteLoop ; Perform infinite loop in case the kernel returns from main
Und dann habe ich in der ckernel.c die folgenden Zeilen auskommentiert, weil es bei mir auf dem Laptop dort irgendwo hängt:
#if 0 /// direct 1st floppy disk if( (cmos_read(0x10)>>4) == 4 ) // 1st floppy 1,44 MB: 0100....b { printformat("1.44 MB 1st floppy is installed\n\n"); flpydsk_set_working_drive(0); // set drive 0 as current drive flpydsk_install(6); // floppy disk uses IRQ 6 k_memset((void*)DMA_BUFFER, 0x0, 0x2400); } else { printformat("1.44 MB 1st floppy not shown by CMOS\n\n"); } /// direct 1st floppy disk #endif
Nach dem Kompilieren hat man einmal die ckernel.sys und die ckernelgrub.sys. Die ckernelgrub.sys ist (laut file):
# file ckernelgrub.sys ckernelgrub.sys: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
und lässt sich bei mir mit GRUB booten Also, es funktioniert, Eingabeprompt erscheint und dort kann man ?, hi eingeben.
Offene Fragen wären jetzt:
- Warum funktioniert es? GDT, LDT werden ja nun von GRUB initialisiert und es passt zufällig?
- Was ist "multiboot" magic number, diese werden an die main() übergeben, sinnvoll?:push eax ; EAX contains "multiboot" magic number (?) push ebx ; EBX contains address of the "multiboot" structure (?) call _main
Weitere Anregungen?
-
Multiboot-kompatible Kernel wollen überprüfen, ob sie überhaupt von einem Multiboot-kompatiblen Bootloader geladen wollen, wenn sie Informationen aus der Multiboot-Struktur erhalten wollen. Deshalb die Magic-Number.
GRUB setzt btw. eine GDT auf, die der Kernel allerdings durch seine eigene ersetzen muss.
-
abc.w schrieb:
Offene Fragen wären jetzt:
- Warum funktioniert es? GDT, LDT werden ja nun von GRUB initialisiert und es passt zufällig?
- Was ist "multiboot" magic number, diese werden an die main() übergeben, sinnvoll?:Lesen hilft. [Moderator: Muss dass unbedingt sein?]
http://lowlevel.brainsware.org/wiki/index.php/Multiboot
http://www.gnu.org/software/grub/manual/multiboot/multiboot.pdf
-
@abc.w: danke vielmals für die rasche Umsetzung. Super!
GRUB setzt btw. eine GDT auf, die der Kernel allerdings durch seine eigene ersetzen muss.
Das macht der Kernel von PrettyOS:
static void init() { //... gdt_install(); // <------ hier passierts idt_install(); isrs_install(); irq_install(); initODA(); timer_install(); keyboard_install(); }
void gdt_install() { /* Setup the GDT pointer and limit */ gdt_register.limit = (sizeof(struct gdt_entry) * NUMBER_GDT_GATES)-1; gdt_register.base = (uint32_t) &gdt; /* GDT GATES - desriptors with pointers to the linear memory address */ gdt_set_gate(0, 0, 0, 0, 0); // NULL descriptor gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); // CODE, privilege level 0 for kernel mode (supervisor) gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); // DATA, privilege level 0 for kernel mode (supervisor) gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // CODE, privilege level 3 for user mode gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // DATA, privilege level 3 for user mode write_tss(5, 0x10, 0x0); // num, ss0, esp0 gdt_flush((uint32_t)&gdt_register); tss_flush(); }
-
Ich möchte einen Vorschlag machen, wie man die "multiboot" Struktur an die main() des Kernels übergibt. In der kernel.asm so:
[Bits 32] global KernelStart global KernelStartFromGRUB extern __bss_start extern __end extern _main ; entry point in ckernel.c ; Flags for GRUB header GRUB_FLAGS equ 0 ; Magic number for GRUB header GRUB_MAGIC_NUMBER equ 0x1BADB002 ; Checksum for GRUB header GRUB_HEADER_CHECKSUM equ -(GRUB_MAGIC_NUMBER + GRUB_FLAGS) section .text ; ld needs that for coff format align 4 MultiBootHeader: ; This is a "multiboot" header for GRUB, this structure must be first (?) dd GRUB_MAGIC_NUMBER dd GRUB_FLAGS dd GRUB_HEADER_CHECKSUM ; This is an entry point for native PrettyOS bootloader KernelStart: mov ax, 0x10 mov ds, ax ; data descriptor --> data, stack and extra segment mov ss, ax mov es, ax xor eax, eax ; null desriptor --> FS and GS mov fs, ax mov gs, ax ; set bss section to zero mov edi, __bss_start mov ecx, __end sub ecx, __bss_start mov al, 0 rep stosb ; repeats instruction decrementing ECX until zero ; and stores value from AL incrementing ES:EDI mov esp, 0x190000 ; set stack below 2 MB limit ; Create argc and argv[] parameters on the stack for the kernel main() function ; Set both to zero because native bootloader is used and no information ; is available here push 0 ; argv is NULL push 0 ; argc is 0 call _main ; ->-> C-Kernel InfiniteLoop: ; Perform infinite loop in case the kernel returns from main cli ; Disable interrupts and try to halt the CPU hlt jmp InfiniteLoop ; This is an entry point for GRUB KernelStartFromGRUB: mov esp, 0x00200000 ; Create argc and argv[] parameters on the stack for the kernel main() function ; Put in argv[] the "multiboot" magic number and the pointer to the "multiboot" structure ; argc must contain the number of valid entries in argv[] push 0 ; argv[N - 1], must be NULL push eax ; argv[1], EAX contains "multiboot" magic number push ebx ; argv[0], EBX contains address of the "multiboot" structure mov eax, esp push eax ; argv push 2 ; argc = 2, because two values in argv[] call _main ; ->-> C-Kernel jmp InfiniteLoop ; Perform infinite loop in case the kernel returns from main
In der ckernel.c kann man dann darauf zugreifen:
int main(int argc, char *argv[]) { init(); // get physical memory which is usable RAM uint16_t num_of_entries = *( (uint16_t*)(ADDR_MEM_ENTRIES) ); // Print the parameters of main. // This is a test to check, if GRUB magic number and structure are on the stack if (2 != argc) { printformat("Unexpected argc: %d", argc); } else if (0 == argv) { printformat("Invalid argv"); } else { if (0x2BADB002u == (uint32_t)(argv[1])) { printformat(">Multiboot flag is valid<"); } else { printformat(">Multiboot flag is not valid<"); } printformat(" mbs_flags = %X", 0[(uint32_t*)(argv[0])]); } ...
Neu sind also die argc und argv in main(): int main(int argc, char *argv[]) und argc und argv werden entsprechend in der kernel.asm gesetzt (s. oben).
Die mbs_flags sind bei mir übrigens 3E7h
Wäre es ok so?
-
Die Idee die Kernel main an eine "normale" main anzupassen ist ja an sich keine schlechte Idee, aber passt das hier?
In einem normalen Programm dienen argc und argv dazu eine beliebige Anzahl an Parametern (in beliebiger Reihenfolge) zu übergeben, die dann vom Programm geprüft/geparst werden. Diese Parameter sind alles Zeichenketten.
Der main Funktion im Kernel wird hier allerdings eine feste Anzahl (von der Reihenfolge abhängiger) Werte übergeben, die nicht zwangsläufig Zeichenketten sind.
Außerdem wird das hinzufügen zusätzlicher Parameter eher schwerer als leichter. Zum einen muss man die Überprüfung von argc anpassen und wenn der neue Parameter irgendwo zwischendrin eingefügt wurde ändern sich die Indizes.
Bei
void main(void* mbd, unsigned int magic)
schreibe ich einfach den zusätzlichen Parameter in die Definition und bin fertig.
-
Die jetztige Anpassung der main()-Funktion ist wirklich nicht sehr gelungen. Wir haben es hier auch nicht mit einer nativen Anwendung, sondern mit einem Kernel und dessen Einsprungspunkt zu tun. Und in Anbetracht der Dinge, dass ihr PrettyOS nun auch per GRUB starten könnt wollt ihr vielleicht in die Versuchung kommen, in Zukunft ein paar Multiboot-Informationen auszulesen.
Vor allem wichtig ist aber, wie schon oben erwähnt, dass ihr überprüft, von wem ihr geladen wurdet (also die Magic-Boot-Nummer überprüft). Wenn ihr in euren eigenen Bootloader dann ebenfalls dem Kernel eine Magicnumber übergibt könnt ihr ganz einfach testen, mit was PrettyOS geladen wurde und dementsprechend handeln: Nehmt ihr nun die Memory-Map von GRUB oder die eigens herausgefundene Größe des RAMs usw. usf.
Und ehe ich's vergesse: Schön, dass sich die verbissene Ablehnung gegen GRUB so lockert
-
Schön, dass sich die verbissene Ablehnung gegen GRUB so lockert
Ich kenne die Vorzüge, die GRUB dem Nutzer bietet, habe nur den Quasi-Zwang seitens taljeth dies verwenden zu müssen bisher abgelehnt, und ich denke das war richtig so.
-
So, es geht voran.
Ich hab (mit etwas Hilfe aus #lost) PrettyOS von GRUB laden können.
Zunächst hab ich abc.ws kernel.asm und unsere miteinander "verschmolzen zu:
[Bits 32] extern __bss_start extern __end extern _main ; entry point in ckernel.c ; Flags for GRUB header GRUB_FLAGS equ 0 ; Magic number for GRUB header GRUB_MAGIC_NUMBER equ 0x1BADB002 ; Checksum for GRUB header GRUB_HEADER_CHECKSUM equ -(GRUB_MAGIC_NUMBER + GRUB_FLAGS) section .text ; ld needs that for coff format align 4 MultiBootHeader: ; This is a "multiboot" header for GRUB, this structure must be first (?) dd GRUB_MAGIC_NUMBER dd GRUB_FLAGS dd GRUB_HEADER_CHECKSUM ; This is an entry point for native PrettyOS bootloader KernelStart: mov ax, 0x10 mov ds, ax ; data descriptor --> data, stack and extra segment mov ss, ax mov es, ax xor ax, ax ; null desriptor --> FS and GS mov fs, ax mov gs, ax mov esp, 0x190000 ; set stack below 2 MiB limit call _main ; ->-> C-Kernel jmp $ ; This is an entry point for GRUB KernelStartFromGRUB: mov esp, 0x00200000 push eax ; EAX contains "multiboot" magic number (?) push ebx ; EBX contains address of the "multiboot" structure (?) call _main jmp $
Dann musste das Linkerscript noch dazu gebracht werden, einen ELF-Kernel zu erzeugen:
OUTPUT_FORMAT(elf32-i386) OUTPUT_ARCH(i386) STARTUP(object_files/kernel/kernel.o) SECTIONS { . = 0x00100000; __kernel_beg = .; .text : { __code_start = .; } . = ALIGN(0x1000); .text1 : { object_files/kernel/data.o(.text) } .data : { __data_start = .; *(.data) } .rodata : { __rodata_start = .; *(.rodata) *(.rdata) } .bss : { __bss_start = .; *(.bss) *(COMMON) } __start_cdi_drivers = .; .cdi : { __cdi_start = .; *(.cdi) } __stop_cdi_drivers = .; __kernel_end = .; __end = .; }
Und zu guter Letzt muss noch GRUB in den Bootsector der Floppy, wozu ich einen Rohling nehme, um mir das zu ersparen:
http://pkeus.bplaced.net/PrettyOS/FloppyImage.imgDort hinein dann die kernel.bin kopieren und booten.
Es ist noch nicht völlig lauffähig, weil die Übergabe der Memory Map noch nicht klappt.