Ontwerp en bouw een besturingssysteem/Hallo wereld/Assembler opstartcode
De eerste echte code die we gaan schrijven is bedoeld zodat de bootloader weet waar onze code moet beginnen met de uitvoering. We moeten daarna zelf zorgen dat we de beschikking krijgen over een stack.
Multiboot header
[bewerken]Omdat de bootloader ervoor zorgt dat we in 32-bit protected mode zitten zodra onze kernel start, beginnen we de assemblercode met het [BITS 32]
directief. Daarna volgt bij ons meteen de multiboot header, want die moet volledig in de eerste 8192 bytes van het kernelbestand voorkomen. De header moet beginnen op een veelvoud van 4 bytes (4-byte aligned) zijn, dus we beginnen met align 4
. Daarna volgt nog een label met de naam multiboot
.
Volgens de multiboot specificatie kunnen we door middel van flags aangeven wat we van de bootloader verwachten en wat de bootloader van ons verwacht. We verwachten dat de bootloader ons informatie over het geheugen geeft (MULTIBOOT_MEMORYINFO
) en dat de bootloader de onderdelen van onze kernel op pagina-aligned adressen laadt (MULTIBOOT_PAGEALIGN
). Ten slotte moeten we de bootloader vertellen waar onze code, data en BSS secties zijn, en waar de uitvoerbare code begint (MULTIBOOT_AOUT
).
start.asm (deel 1)
[BITS 32]
section .text
; De multibootheader moet zo vroeg mogelijk in het bestand komen,
; en 4-byte aligned zijn.
align 4
multiboot:
; We gebruiken de volgende flags:
MULTIBOOT_PAGEALIGN equ 1<<0 ; Onderdelen pagina-aligned laden.
MULTIBOOT_MEMORYINFO equ 1<<1 ; Bootloader geeft geheugeninformatie.
MULTIBOOT_AOUT equ 1<<16 ; Wij zeggen waar alles zit en begint.
; De eerste drie velden van de multiboot header:
MULTIBOOT_MAGIC equ 0x1BADB002
MULTIBOOT_FLAGS equ MULTIBOOT_PAGEALIGN | MULTIBOOT_MEMORYINFO | MULTIBOOT_AOUT
MULTIBOOT_CHECKSUM equ -(MULTIBOOT_MAGIC + MULTIBOOT_FLAGS)
dd MULTIBOOT_MAGIC
dd MULTIBOOT_FLAGS
dd MULTIBOOT_CHECKSUM
extern code, bss, end
; Omdat we de MULTIBOOT_AOUT flag hebben opgegeven, moeten we hier
; de fysieke adressen van de verschillende onderdelen van onze
; hoofdexecutable invullen. Tenminste, ons link-script doet dat voor ons.
dd multiboot
dd code
dd bss
dd end
dd start
...
Het equ
commando maakt een macro met de opgegeven naam en waarde. Met het dd
command wordt vervolgens die waarde als 32-bit waarde direct op dit plek in het uiteindelijke bestand geschreven. Ten slotte het extern
commando, waarmee wordt aangegeven dat die symbolen (code
, bss
en end
) ergens anders gedefinieerd zijn (in ons geval in het linkbestand uit het volgende hoofdstuk).
Uitvoerbare code
[bewerken]Het volgende deel van de code is waar de kernel écht start. We zetten de stack (van maximaal 8 KiB) aan het einde van de kernel in de BSS sectie. Om de stack dan ook werkelijk te gebruiken moeten we een pointer ernaar toe in het ESP
register laden. Daarna doen we voorlopig nog niks, dus zetten we daar een oneindige loop.
start.asm (deel 2)
...
; Onze startcode. Hier zetten we onze stack op
; en gaan we (met 'jmp $') een eindeloze loop in.
global start
start:
mov esp, _kernel_stack
jmp $
; In de BBS sectie komt de data die bij de uitvoering op 0 moet worden
; ingesteld. Dit is dan ook een goede plek voor onze stack. De stack groeit
; van het '_kernel_stack' label naar beneden (richting geheugenadres 0),
; en mag nu maximaal 8 KiB groot worden.
section .bss
resb 0x2000
_kernel_stack:
Het global
commando maakt het symbool start
ook beschikbaar in de andere codebestanden die we nog gaan maken. Met het mov
commando wordt het adres van het _stack
symbool in het register ESP
gezet. Dit register is speciaal voor dit doel bestemd, zie het voorgaande hoofdstuk Registers. Met jmp
springt de uitvoering naar een andere plek. In dit geval springt de uitvoering naar $
, wat de instructie zelf aangeeft. Dit geeft dus een oneindige loop.
De BSS sectie start bij section .bss
, en met resb 4096
wordt er hier 4096 bytes aan ruimte gereserveerd. Dit gebruiken we voor de stack.
Voor de duidelijkheid volgt hier nog een keer het complete bestand.
start.asm (hele document)
[BITS 32]
section .text
; De multibootheader moet zo vroeg mogelijk in het bestand komen,
; en 4-byte aligned zijn.
align 4
multiboot:
; We gebruiken de volgende flags:
MULTIBOOT_PAGEALIGN equ 1<<0 ; Onderdelen pagina-aligned laden.
MULTIBOOT_MEMORYINFO equ 1<<1 ; Bootloader geeft geheugeninformatie.
MULTIBOOT_AOUT equ 1<<16 ; Wij zeggen waar alles zit en begint.
; De eerste drie velden van de multiboot header:
MULTIBOOT_MAGIC equ 0x1BADB002
MULTIBOOT_FLAGS equ MULTIBOOT_PAGEALIGN | MULTIBOOT_MEMORYINFO | MULTIBOOT_AOUT
MULTIBOOT_CHECKSUM equ -(MULTIBOOT_MAGIC + MULTIBOOT_FLAGS)
dd MULTIBOOT_MAGIC
dd MULTIBOOT_FLAGS
dd MULTIBOOT_CHECKSUM
extern code, bss, end
; Omdat we de MULTIBOOT_AOUT flag hebben opgegeven, moeten we hier
; de fysieke adressen van de verschillende onderdelen van onze
; hoofdexecutable invullen. Tenminste, ons link-script doet dat voor ons.
dd multiboot
dd code
dd bss
dd end
dd start
; Onze startcode. Hier zetten we onze stack op
; en gaan we (met 'jmp $') een eindeloze loop in.
global start
start:
mov esp, _kernel_stack
jmp $
; In de BBS sectie komt de data die bij de uitvoering op 0 moet worden
; ingesteld. Dit is dan ook een goede plek voor onze stack. De stack groeit
; van het '_kernel_stack' label naar beneden (richting geheugenadres 0),
; en mag nu maximaal 8 KiB groot worden.
section .bss
resb 0x2000
_kernel_stack: