Again, my example/actual bootstrap code, maybe it helps?
Code:
.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
.section .multiboot
.long MAGIC
.long FLAGS
.long CHECKSUM
.section .stack, "aw", @nobits
stackBottom:
.skip 16384
stackTop:
.set KVIRT_BASE, 0xC0000000
.set KPAGE_NUM, (KVIRT_BASE >> 12)
.section .bss
.align 0x1000
pageDirectory:
.skip 0x1000 # Page directory
pageTables:
.skip 0x1000 # First identity-mapped 4MiB
.skip 0x1000 * 256 # 3GiB to 4GiB area
gdtPtr:
.skip 6 # GDT Pointer
gdt:
.skip 8 * 3 # Actual GDT
idtPtr:
.skip 6 # IDT Pointer
idt:
.skip 8 * 256 # Actual IDT
.section .boot
.global __start__
__start__:
# A Multiboot-compliant bootloader just yielded
# execution to the OS here. At this moment,
# everything is loaded at 1MiB, but the code
# thinks it's on 3GiB! The solution? Subtract
# 3GiB from *each* address used, so it resolves
# to the correct physical address. That's the job
# of the 'subl $KVIRT_BASE, %edx' stuff all around
# this function. Once the paging structures are fully
# calculated and loaded, __start__() can work with virtual
# addresses. It then passes control to realStart(), the
# routine responsible for parsing the Multiboot information (TODO),
# setting up the descriptor tables (GDT and IDT), and
# finally calling KernelInit. If something fails,
# criticalError() is a minimalistic asm version of KTPipe
# that writes some string in red/white to VGA memory and halts.
# Load the stack (I can't live up from the registers!)
movl $stackTop, %esp
subl $KVIRT_BASE, %esp
# Save Multiboot Information (%ebx) and Bootloader Magic (%eax)
push %eax
push %ebx
# Map the first 4MiB to the first page table
movl $pageDirectory, %edx
subl $KVIRT_BASE, %edx
movl $pageTables, %eax
subl $KVIRT_BASE, %eax
orl $0x00000003, %eax
movl %eax, (%edx)
# Identity map the first page table
movl $pageTables, %edi
subl $KVIRT_BASE, %edi
movl $1024, %ecx
movl %ecx, %ebx
.identmap:
movl %ebx, %eax
subl %ecx, %eax
movl %eax, %esi
shll $12, %esi
orl $0x00000003, %esi
movl $4, %edx
mull %edx
movl %edi, %edx
addl %eax, %edx
movl %esi, (%edx)
loop .identmap
# Map the first Gigabyte to 3GiB upwards
movl $pageTables, %edi
subl $KVIRT_BASE, %edi
addl $0x1000, %edi
movl $256, %ecx
movl $0, %edx
.highmap:
movl %ecx, %ebx
movl $1024, %ecx
.highmap2:
movl %edx, %eax
shll $12, %eax
orl $0x00000003, %eax
movl %eax, (%edi)
incl %edx
addl $4, %edi
loop .highmap2
movl %ebx, %ecx
loop .highmap
# Load the higher-half tables
movl $pageDirectory, %edx
subl $KVIRT_BASE, %edx
addl $3072, %edx
movl $256, %ecx
movl %ecx, %ebx
.filltop:
movl %ebx, %esi
subl %ecx, %esi
movl %edx, %edi
movl $0x1000, %eax
mull %esi
movl %edi, %edx
movl $pageTables, %edi
subl $KVIRT_BASE, %edi
addl $0x1000, %edi
addl %eax, %edi
orl $0x00000003, %edi
movl %edx, %ebp
movl $4, %eax
mull %esi
movl %ebp, %edx
addl %edx, %eax
movl %edi, (%eax)
loop .filltop
# Load the page directory
movl $pageDirectory, %edx
subl $KVIRT_BASE, %edx
movl %edx, %cr3
# Enable paging
movl %cr0, %edx
orl $0x80000000, %edx
movl %edx, %cr0
# Jump to some code that *really* assumes it's on $KVIRT_BASE
movl $realStart, %edx
jmp *%edx
.text
realStart:
# Load the virtual value of $stackTop
# And, to comply with the ABI, load %esp into %ebp
addl $KVIRT_BASE, %esp
movl %esp, %ebp
# Setup the Global Descriptor Table (GDT) Pointer
movl $gdtPtr, %edx
movl $gdt, %eax
movw $23, (%edx)
movl %eax, 2(%edx)
# Load the Null GDT Entry
push $0
push $0
push $0
push $0
push $0
call setGDTEntry
addl $20, %esp
# Load the Kernel Code GDT Entry
push $0xCF
push $0x9A
push $0xFFFFFFFF
push $0
push $1
call setGDTEntry
addl $20, %esp
# Load the Kernel Data GDT Entry
push $0xCF
push $0x92
push $0xFFFFFFFF
push $0
push $2
call setGDTEntry
addl $20, %esp
# Actually load the full GDT
movl $gdtPtr, %eax
lgdt (%eax) # Do it!
movw $0x10, %ax # Load the data segment registers
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
ljmp $0x08, $.gdtReady # Some black magic to load %cs
.gdtReady:
# Do the same as above, but with the Interrupt Descriptor Table (IDT) Pointer
movl $idtPtr, %edx
movl $idt, %eax
movw $384, (%edx)
movl %eax, 2(%edx)
# We'll load *all* the ISRs, so we need to use as less code as possible,
# but stack memory is infinite, as far as the processor is concerned :)
subl $192, %esp
#define ISR(no, who) \
movl $__inthand_ ## who ## __, %eax; \
movl %eax, no(%esp);
ISR(0, exc0)
ISR(4, exc1)
ISR(8, exc2)
ISR(12, exc3)
ISR(16, exc4)
ISR(20, exc5)
ISR(24, exc6)
ISR(28, exc7)
ISR(32, exc8)
ISR(36, exc9)
ISR(40, exc10)
ISR(44, exc11)
ISR(48, exc12)
ISR(52, exc13)
ISR(56, exc14)
ISR(60, exc15)
ISR(64, exc16)
ISR(68, exc17)
ISR(72, exc18)
ISR(76, exc19)
ISR(80, exc20)
ISR(84, exc21)
ISR(88, exc22)
ISR(92, exc23)
ISR(96, exc24)
ISR(100, exc25)
ISR(104, exc26)
ISR(108, exc27)
ISR(112, exc28)
ISR(116, exc29)
ISR(120, exc30)
ISR(124, exc31)
ISR(128, irq0)
ISR(132, irq1)
ISR(136, irq2)
ISR(140, irq3)
ISR(144, irq4)
ISR(148, irq5)
ISR(152, irq6)
ISR(156, irq7)
ISR(160, irq8)
ISR(164, irq9)
ISR(168, irq10)
ISR(172, irq11)
ISR(176, irq12)
ISR(180, irq13)
ISR(184, irq14)
ISR(188, irq15)
#undef ISR
# Actually set the IDT entries
movl $48, %ecx
.idtLoop:
movl %ecx, %ebx
movl $48, %esi
subl %ebx, %esi
movl (%esp, %esi, 4), %eax
push $0x8E
push $0x08
push %eax
push %esi
call setIDTEntry
addl $16, %esp
movl %ebx, %ecx
loop .idtLoop
addl $192, %esp # Free up that stack space
# Load the IDT and we'll be done
movl $idtPtr, %eax
lidt (%eax) # Do it!
# Reconfigure the PICs, so stupid stuff doesn't happens
inb $0x21, %al
movb %al, %bl
inb $0xA1, %al
movb %al, %cl
movb $0x11, %al
outb %al, $0x20
outb %al, $0xA0
movb $0x20, %al
outb %al, $0x21
movb $0x28, %al
outb %al, $0xA1
movb $0x04, %al
outb %al, $0x21
movb $0x02, %al
outb %al, $0xA1
movb $0x01, %al
outb %al, $0x21
outb %al, $0xA1
movb %bl, %al
outb %al, $0x21
movb %cl, %al
outb %al, $0xA1
# Get Multiboot Information (%ebx) and Bootloader Magic (%eax)
pop %ebx
pop %eax
# Check the Bootloader magic. If it's invalid, panic!
cmp $0x2BADB002, %eax
je .noerror
# Error! **** it up!
push $.ENOTMULTIBOOT # "Not using a Multiboot-compliant bootloader"
push $0x01 # Error Code
call criticalError
.noerror:
# Parse the first-level Multiboot Structure (TODO)
addl $KVIRT_BASE, %ebx # Physical to Virtual
# Call trivial global constructors
call _init
# Pass it to KernelInit()...
push %ebx
# Should I put a comment here? Anyway, I did it.
call KernelInit
# If KernelInit() somehow returns, halt the machine.
# Non-maskable interrupt? Don't worry! Just loop forever.
.hang:
cli
hlt
jmp .hang
setGDTEntry:
push %ebp
movl %esp, %ebp
push %esi # We need some extra memory
movl 8(%ebp), %eax # GDT Index
movl $gdt, %ecx
movl 12(%ebp), %edx # The base
movw %dx, 2(%ecx, %eax, 8) # Set low base
shrl $16, %edx
movb %dl, 4(%ecx, %eax, 8) # Set middle base
movb %dh, 7(%ecx, %eax, 8) # Set high base
movl 16(%ebp), %edx # The limit
movw %dx, (%ecx, %eax, 8) # Set low limit
movl 24(%ebp), %esi # Granularity
shrl $16, %edx
andl $0x0F, %edx
andl $0xF0, %esi
orl %esi, %edx
movb %dl, 6(%ecx, %eax, 8) # Set granularity
movl 20(%ebp), %edx # Access
movb %dl, 5(%ecx, %eax, 8) # Set access
pop %esi
movl %ebp, %esp
pop %ebp
ret
setIDTEntry:
push %ebp
movl %esp, %ebp
movl 8(%ebp), %eax # IDT Index
movl $idt, %ecx
movl 12(%ebp), %edx # The base
movw %dx, (%ecx, %eax, 8) # Set low base
shrl $16, %edx
movw %dx, 6(%ecx, %eax, 8) # Set high base
movl 16(%ebp), %edx # The selector
movw %dx, 2(%ecx, %eax, 8) # Set selector
movl 20(%ebp), %edx # Flags
movb %dl, 5(%ecx, %eax, 8) # Set flags
movb $0, 4(%ecx, %eax, 8) # Always 0
movl %ebp, %esp
pop %ebp
ret
criticalError:
# %ebx is the offset over VGA memory used
# by the criticalPrint* functions.
movl $0, %ebx
# "Fatal Error Code "
movl $.EERROR, %eax
call criticalPrintString
pop %eax # Error Code
call criticalPrintHex
movb $0x3A, %al # ':' Character
call criticalPrintChar
movb $0x20, %al # ' ' Character
call criticalPrintChar
pop %eax # Error Message
call criticalPrintString
movl $.EHALT, %eax
call criticalPrintString
# Halt
cli
hlt
criticalPrintString:
# Parameters:
# %eax: Address of null-terminated string to print
.strloop:
movb (%eax), %al
cmp $0x00, %al
je .strloopend
call criticalPrintChar
incl %eax
incl %ebx
jmp .strloop
.strloopend:
ret
criticalPrintHex:
# Parameters:
# %eax: Integer to print
movb $0x30, %al # '0' Character
call criticalPrintChar
incl %ebx
movb $0x78, %al # 'x' Character
call criticalPrintChar
incl %ebx
movl $8, %ecx
.hexloop:
movl %ecx, %esi
decl %esi
push %eax
movl %eax, %edi
movl $4, %eax
mull %esi
xchg %eax, %ecx
shrl %cl, %edi
xchg %eax, %ecx
movl %edi, %edx
push %ecx
movl $2, %ecx
.hexbitloop:
push %edx
andb $0x0F, %dl
# Get the real value...
.hextest:
cmpb $0x0A, %dl
jb .hextestdecimal
jae .hextesthexa
.hextestdecimal:
addb $0x30, %dl # Calculates character between '0' and '9'
jmp .hextestend
.hextesthexa:
subb $0x0A, %dl # For offsetting
addb $0x41, %dl
jmp .hextestend
.hextestend:
movb %dl, %al
call criticalPrintChar
incl %ebx
pop %edx
shrl $4, %edx
loop .hexbitloop
pop %ecx
pop %eax
loop .hexloop
ret
criticalPrintChar:
# Parameters:
# %al: Character to print
# %ebx: Offset over VGA Memory
# Uses:
# %dx: Actual value written to VGA Memory
movb $0x4F, %dh # White text on red background
movb %al, %dl # Mix it...
movw %dx, 0xB8000(,%ebx, 2) # Print!
ret
.section rodata
# Concatenated on every error
.EERROR:
.asciz "Fatal Error Code "
.EHALT:
.asciz ". System halted! Please reboot manually."
# The actual errors
.ENOTMULTIBOOT:
.asciz "Not using a Multiboot-compliant bootloader"