No problem. The code has 3 parts, a linker script, and a makefile:
main.s: First the 32-bit/64-bit assembly:
Code: Select all
global loader
global _dbgwrite
extern _start_code16
extern _end_code16
extern BuildPageTables
extern MoveMultiboot
%define ALIGN (01b) ; We need the modules aligned
%define MEMINFO (010b) ; We need memory info
%define VIDINFO (0100b) ; We need video info
%define FLAGS (ALIGN | MEMINFO | VIDINFO) ; Flags passed to multiboot
%define MAGIC (01BADB002h) ; Multiboot magic number
%define CHECKSUM -(MAGIC + FLAGS) ; Multiboot Checksum must be 0 when added to magic and flags
%define STACK_SIZE 0200000h
%define MULTIBOOT_BUFFER_SIZE 0F0000h
%define KERNEL_BUFFER_SIZE 0200000h
%define CODE16_RELOC 01000h
%define CODE16_PNP_BUFFER 02000h
%define PAGE_TABLE_MASK 11011b
%define NEWLINE 0Ah
%macro dbgwrite 1 ;writes a string to port E9, which is debug output in bochs
push %1
call _dbgwrite
add esp, 4
%endmacro
section .text
align 4
dd MAGIC
dd FLAGS
dd CHECKSUM
dq 0 ; reserve space for load information
dq 0
dd 0
dd 0 ; set linear graphics mode
dd 0 ; no preference for width
dd 0 ; no preference for height
dd 0 ; no preference for bits per pixel (BPP)
load_str db 'Double Load Succeeded.',NEWLINE, 0
no64_str db 'No 64-bit Support Detected! What the ****?!',NEWLINE, 0
ret16_str db 'Successfully returned from 16-bit code.',NEWLINE, 0
multiboot_info_fail_str db 'Multiboot Info Loading Failed.',NEWLINE, 0
kernel_invalid_str db 'The Kernel File Was Invalid.',NEWLINE, 0
BITS 32
loader:
mov esp, _end_stack ; setup a stack
dbgwrite load_str
check_for_64:
pusha ; save all registers before cpuid
mov eax, 080000001h
cpuid
test edx, (1<<29) ; check if the processor supports long (64-bit) mode
jnz move_multiboot_info
dbgwrite no64_str
move_multiboot_info:
popa ; restore all registers after cpuid
push multiboot_buffer ; push address of multiboot_buffer
push ebx ; push address of origional multiboot info
call MoveMultiboot
add esp, 8 ; adjust stack after call
cmp eax, 0 ; check for success (return value = 0)
je move_code16
dbgwrite multiboot_info_fail_str
hlt
move_code16: ; moves the 16 bit code below the 1mb mark
mov esi, _start_code16 ; Start of 16-bit code in this file
mov edi, CODE16_RELOC ; Address 16-bit code needs to be loaded at
mov ecx, _end_code16
sub ecx, _start_code16 ; calculate size of 16-bit code
test ecx, 01b ; Checks if the size is divisible by 2
jz move_code16_fast ; If it is, a faster version of the move can be used
cld ; clear direction flag
rep movsb ; move the code one byte at a time
jmp enter_code16
move_code16_fast: ; faster version of the code16 moving code
shr ecx, 1 ; divide size by 2
test ecx, 01b ; Check if the size is still divisible by 2
jz move_code16_even_faster ; If it is, an even faster version of the move can be used
cld ; clear direction flag
rep movsw ; move the code one word at a time
jmp enter_code16
move_code16_even_faster:
shr ecx, 1 ; divide the size by 2
cld
rep movsd ; move the code one dword at a time
enter_code16: ; enters the 16-bit code
mov edx, ret_code16 ; tell 16bit code where to returnmov eax, cr0
jmp CODE16_RELOC ; enter 16 bit code. NOTE: We are not in real mode yet. the 16-bit code will switch to real mode.
ret_code16:
dbgwrite ret16_str
build_page_tables: ; build temporary page tables
mov eax, 0 ; setting tables to 0
mov edi, pml4 ; we are clearing all page structures, starting with pml4
mov ecx, (4096*6) ; clearing 6 4096 byte structures (6 pages)
cld
rep stosb ; clear the structures
push pt1
push pd2
push pd1
push pdp2
push pdp1
push pml4
push kernel_buffer
push entry_point
push multiboot_buffer
call BuildPageTables
add esp, 9*4 ; Adjust stack after function call
cmp eax, 0
je enter_64
dbgwrite kernel_invalid_str
hlt
enter_64:
mov eax, cr4
or eax, 10100000b ; set physical address extension (PAE) and page size extension (PSE) flags
mov cr4, eax
mov ecx, 0C0000080h ; select EFER Model Specific Register
rdmsr
or eax, 0100h ; set long mode enable (LME) flag
wrmsr
mov eax, pml4
mov cr3, eax ; set address of pml4 (cr3)
mov eax, cr0
or eax, 080000000h ; set paging (PG) flag, which automatically sets LMA
mov cr0, eax
lgdt [gdtr]
jmp 08h:in_64
BITS 64
in_64:
mov rsp, _end_stack ; setup stack
mov rbp, rsp
enter_kernel:
mov rdi, multiboot_buffer
call enter_kernel_stub ; Call into the kernel, located at start of higher half
hlt ; if the kernel returns, halt
jmp $
enter_kernel_stub:
mov rax, [entry_point]
jmp rax ; Jump to kernel
BITS 32
_dbgwrite:
push ebp
mov ebp, esp
push ebx
push eax
mov ebx, dword [ebp+8]
_dbgwrite_loop:
mov al, byte [ebx]
inc ebx
cmp al, 0
je _dbgwrite_ret
out 0E9h, al
jmp _dbgwrite_loop
_dbgwrite_ret:
pop eax
pop ebx
pop ebp
ret
gdtr:
dw (_end_gdt-gdt)-1
dd gdt
gdt:
dq 0 ; define null segment
dd 0 ; 64-bit code segment
db 0
db 10011010b
db 10100000b
db 0
dd 0 ; 64-bit data segment
db 0
db 10010010b
db 10100000b
db 0
_end_gdt:
section .data
incbin "main16.bin" ; include 16-bit binary code
section .bss
entry_point resq 1;
align 4096
pml4:
resq 512
pdp1:
resq 512
pdp2:
resq 512
pd1:
resq 512
pd2:
resq 512
pt1:
resq 512
pt2:
resq 512
multiboot_buffer:
resb MULTIBOOT_BUFFER_SIZE
_end_multiboot_buffer:
align 4096
kernel_buffer:
resb KERNEL_BUFFER_SIZE
_end_kernel_buffer:
stack:
resb STACK_SIZE
_end_stack:
main16.s: Here is some 16-bit assembly used by the previous 32-bit/64-bit assembly(it is designed to be used for changing video modes, but I didn't write that yet).
Code: Select all
%define LOAD_ADDR 01000h ; address this code is loaded at
ORG LOAD_ADDR
BITS 32
%define PNP_BUFFER_LOCATION LOAD_ADDR + 01000h
entry:
mov dword [return_loc], edx ; save return location
lgdt [gdtr] ; load gdt
jmp 08h:do_pmode16_jmp
do_pmode16_jmp:
jmp 018h:pmode16 ; switch to 16-bit pmode with a far call
BITS 16
pmode16:
mov ax, 020h
mov ss, ax ; load the stack segment with a 16-bit segment
mov eax, cr0
and eax, 0FFFFFFFEh
mov cr0, eax ; clear the protected mode (PE) bit
jmp 0h:realmode ; switch to real mode with a far call
realmode:
mov ax, cs ; set all segments to the code segment
mov ds, ax
mov ss, ax
mov es, ax
pmode32: ; switch to real mode
lgdt [gdtr]
mov eax, cr0
or al, 1
mov cr0, eax ; set protected mode (PE) bit
jmp 018h:return_pmode16 ; jump into 32-bit code
BITS 16
return_pmode16:
jmp 08h:return
BITS 32
return: ; return to the rest of double
mov eax, 010h
mov ds, eax
mov ss, eax
mov es, eax
mov edx, dword [return_loc]
mov dword [rewritable_jmp+1], edx ; rewrite the jump to the return location
rewritable_jmp:
jmp 08h:0FFFFFFFh ; won't actually jump to this addr, this value is overwritten
return_loc dd 0
gdtr:
dw (_end_gdt-gdt)-1
dd gdt
gdt:
dq 0 ; null segment
dw 0FFFFh ; 32-bit code segment
dw 0
db 0
db 10011010b
db 11001111b
db 0
dw 0FFFFh ; 32-bit data segment
dw 0
db 0
db 10010010b
db 11001111b
db 0
dw 0FFFFh ; 16-bit code segment
dw 0
db 0
db 10011010b
db 00001111b
db 0
dw 0FFFFh ; 16-bit data segment
dw 0
db 0
db 10010010b
db 00001111b
db 0
_end_gdt:
c.c: Heres the C portion of the code. It does the actual interpreting of the ELF64 header, and builds page tables.
Code: Select all
#pragma pack(1)
//Declare dbgwrite function from assembly
void _dbgwrite(char* str);
//Define a macro for dbgwrite
#define dbgwrite(str) _dbgwrite(str)
typedef struct _Module
{
void* Start;
void* End;
char* Name;
unsigned int Reserved;
} Module;
typedef struct _MemoryMap
{
unsigned int Size;
unsigned long long Address;
unsigned long long Length;
unsigned int Type;
} MemoryMap;
typedef struct _Drive
{
unsigned int Size;
unsigned char Number;
unsigned char Mode;
unsigned char Cylinders;
unsigned char Heads;
unsigned char Sectors;
unsigned short Ports[];
} Drive;
typedef struct _VBEControlInfo
{
unsigned char Signature[4];
unsigned short Version;
unsigned short OemString[2];
unsigned char Capabilities[4];
unsigned short VideoModes[2];
unsigned short TotalMemory;
} VBEControlInfo;
typedef struct _VBEModeInfo
{
unsigned short Attributes;
unsigned char WinA;
unsigned char WinB;
unsigned short Granularity;
unsigned short WinSize;
unsigned short SegmentA;
unsigned short SegmentB;
unsigned short VBEFar;
unsigned short Pitch;
unsigned short XRes;
unsigned short YRes;
unsigned char WChar, YChar, Planes, BPP, Banks;
unsigned char MemoryModel, BackSize, ImagePages;
unsigned char Reserved;
unsigned char RedMask, RedPosition, GreenMask, GreenPosition, BlueMask, BluePosition, RSVMask, RSVPosition, DirectcolorAttributes;
unsigned int Physbase;
unsigned int Reserved1;
unsigned short Reserved2;
} VBEModeInfo;
typedef struct _Memory
{
unsigned int Lower;
unsigned int Upper;
} Memory;
typedef struct _BootDevice
{
unsigned char Drive;
unsigned char Partition1;
unsigned char Partition2;
unsigned char Partition3;
} BootDevice;
typedef struct _Modules
{
unsigned int Count;
Module* Address;
} Modules;
typedef struct _Symbols
{
unsigned int Useless[4];
} Symbols;
typedef struct _MemoryMaps
{
unsigned int Length;
void* Address;
} MemoryMaps;
typedef struct _Drives
{
unsigned int Length;
Drive* Address;
} Drives;
typedef struct _ApmTable
{
unsigned short Version;
unsigned short CodeSegment;
unsigned int Offset;
unsigned short CodeSegment16;
unsigned short DataSegment;
unsigned short Flags;
unsigned short CodeSegmentLength;
unsigned short CodeSegment16Length;
unsigned short DataSegmentLength;
} ApmTable;
typedef struct _VBE
{
VBEControlInfo* ControlInfo;
VBEModeInfo* ModeInfo;
unsigned short Mode;
unsigned short Segment;
unsigned short Offset;
unsigned short Length;
} VBE;
typedef struct _MultibootInfo
{
unsigned int Flags;
Memory Memory;
BootDevice BootDevice;
char* CommandLine;
Modules Modules;
Symbols Symbols;
MemoryMaps MemoryMaps;
Drives Drives;
void* ConfigTable;
char* BootloaderName;
ApmTable* ApmTable;
VBE VBE;
} MultibootInfo;
#define MEMORY_PRESENT (1<<0)
#define BOOTDEVICE_PRESET (1<<1)
#define COMMANDLINE_PRESENT (1<<2)
#define MODULES_PRESENT (1<<3)
#define SYMBOLS1_PRESENT (1<<4)
#define SYMBOLS2_PRESENT (1<<5)
#define MEMORYMAP_PRESENT (1<<6)
#define DRIVES_PRESENT (1<<7)
#define CONFIGTABLE_PRESENT (1<<8)
#define BOOTLOADERNAME_PRESENT (1<<9)
#define APMTABLE_PRESENT (1<<10)
#define VBEINFO_PRESENT (1<<11)
#define REQ_FLAGS (MEMORY_PRESENT | MEMORYMAP_PRESENT | MODULES_PRESENT | COMMANDLINE_PRESENT)
//Define some basic string functions. These aren't as efficient as the assembly versions, but they'll do.
int strlen(const char* str)
{
for(int i = 0; ; i++)
{
if(str[i] == 0)
return i;
}
}
void strcpy(char* dst, const char* src)
{
int len = strlen(src)+1;
for(int i = 0; i < len; i++)
{
dst[i] = src[i];
}
}
void strncpy(char* dst, const char* src, int len)
{
for(int i = 0; i < len; i++)
{
dst[i] = src[i];
}
}
int strcmp(const char* str1, const char* str2)
{
int len = strlen(str1);
if(len != strlen(str2))
return 0;
for(int i = 0; i < len; i++)
{
if(str1[i] != str2[i])
return 0;
}
return 1;
}
void memcpy(void* dst, const void* src, int size, int count)
{
for(int i = 0; i < (size*count); i++)
{
((unsigned char*)dst)[i] = ((unsigned char*)src)[i];
}
}
int MoveMultiboot(MultibootInfo* multiboot_addr, MultibootInfo* buffer_addr)
{
*buffer_addr = *multiboot_addr; //Move the main multiboot structure (duh)
if((multiboot_addr->Flags & REQ_FLAGS) != REQ_FLAGS) //Check if the required entries are present
return 1;
void* currentBufferAddr = buffer_addr+sizeof(MultibootInfo); //Gets the first address in the multiboot buffer avaliable for writing
if(multiboot_addr->Flags & COMMANDLINE_PRESENT)
{
strcpy(currentBufferAddr, multiboot_addr->CommandLine); //Copy the command line to the buffer
buffer_addr->CommandLine = currentBufferAddr; //Change the address of the command line in the new multiboot structure
currentBufferAddr += strlen(multiboot_addr->CommandLine)+1; //Increment the current buffer address
}
if(multiboot_addr->Flags & MODULES_PRESENT)
{
if(multiboot_addr->Modules.Count == 0) //Makes sure module count != 0, this should never happen
return 2;
memcpy(currentBufferAddr, multiboot_addr->Modules.Address, sizeof(Module), multiboot_addr->Modules.Count); //Copy the module structures
buffer_addr->Modules.Address = currentBufferAddr; //Change the address to module information in the new multiboot structure
currentBufferAddr += sizeof(Module)*(multiboot_addr->Modules.Count); //Increment the current buffer address
for(int i = 0; i < multiboot_addr->Modules.Count; i++)
{
strcpy(currentBufferAddr, multiboot_addr->Modules.Address[i].Name);
buffer_addr->Modules.Address[i].Name = currentBufferAddr;
currentBufferAddr += strlen(multiboot_addr->Modules.Address[i].Name)+1;
}
}
if(multiboot_addr->Flags & MEMORYMAP_PRESENT)
{
memcpy(currentBufferAddr, multiboot_addr->MemoryMaps.Address, 1, multiboot_addr->MemoryMaps.Length); //Copy all the memory maps
buffer_addr->MemoryMaps.Address = currentBufferAddr; //Change the address to the copies
currentBufferAddr += multiboot_addr->MemoryMaps.Length; //Increment the current buffer address
}
if(multiboot_addr->Flags & DRIVES_PRESENT)
{
memcpy(currentBufferAddr, multiboot_addr->Drives.Address, 1, multiboot_addr->Drives.Length); //Copy the drives
buffer_addr->Drives.Address = currentBufferAddr; //Change the address to the copies
currentBufferAddr += multiboot_addr->Drives.Length; //Increment the current buffer address
}
if(multiboot_addr->Flags & CONFIGTABLE_PRESENT)
multiboot_addr->Flags &= ~CONFIGTABLE_PRESENT; //clear the flag and act like we didn't get the config table
if(multiboot_addr->Flags & BOOTLOADERNAME_PRESENT)
{
strcpy(currentBufferAddr, multiboot_addr->BootloaderName);
buffer_addr->BootloaderName = currentBufferAddr;
currentBufferAddr += strlen(multiboot_addr->BootloaderName)+1;
}
if(multiboot_addr->Flags & APMTABLE_PRESENT)
{
memcpy(currentBufferAddr, multiboot_addr->ApmTable, sizeof(ApmTable), 1);
buffer_addr->ApmTable = currentBufferAddr;
currentBufferAddr += sizeof(ApmTable);
}
if(multiboot_addr->Flags & VBEINFO_PRESENT)
{
memcpy(currentBufferAddr, multiboot_addr->VBE.ControlInfo, sizeof(VBEControlInfo), 1);
buffer_addr->VBE.ControlInfo = currentBufferAddr;
currentBufferAddr += sizeof(VBEControlInfo);
memcpy(currentBufferAddr, multiboot_addr->VBE.ModeInfo, sizeof(VBEModeInfo), 1);
buffer_addr->VBE.ModeInfo = currentBufferAddr;
currentBufferAddr += sizeof(VBEModeInfo);
}
}
#define PAGE_TABLE_PERM ((1<<0) | (1<<1))
#define PAGE_TABLE_PERM_NOCACHE (PAGE_TABLE_PERM | (1<<3) | (1<<4))
#define PAGE_TABLE_LARGE_FLAG (1<<7)
typedef unsigned long long PML4;
typedef unsigned long long PDP;
typedef unsigned long long PD;
typedef unsigned long long PT;
typedef struct _ElfHeader
{
unsigned char Identification[16];
unsigned short Type;
unsigned short Machine;
unsigned long Version;
unsigned long long Entry;
unsigned long long ProgramHeaderTableOffset;
unsigned long long SectionHeaderTableOffset;
unsigned long Flags;
unsigned short HeaderSize;
unsigned short ProgramHeaderEntrySize;
unsigned short NumberProgramHeaderEntry;
unsigned short SectionHeaderEntrySize;
unsigned short NumberSectionHeaderEntry;
unsigned short SectionNameTableIndex;
} ElfHeader;
typedef struct _ElfProgramHeader
{
unsigned long Type;
unsigned long Flags;
unsigned long long Offset;
unsigned long long VAddr;
unsigned long long PAddr;
unsigned long long FileSize;
unsigned long long MemSize;
unsigned long long Alignment;
} ElfProgramHeader;
void* align(void* addr)
{
long iaddr = (long)addr;
if(iaddr % 4096 == 0)
return (void*)iaddr;
iaddr += (4096-(iaddr % 4096));
}
int BuildPageTables(MultibootInfo* multiboot, unsigned long long* entrypoint, void* kernel_buffer, PML4* pml4, PDP* pdp1, PDP* pdp2, PD* pd1, PD* pd2, PT* pt1)
{
pml4[0] = PAGE_TABLE_PERM_NOCACHE | (long)pdp1; //Builds the page table structure
pml4[256] = PAGE_TABLE_PERM_NOCACHE | (long)pdp2;
pdp1[0] = PAGE_TABLE_PERM_NOCACHE | (long)pd1;
pdp2[0] = PAGE_TABLE_PERM_NOCACHE | (long)pd2;
pd2[0] = PAGE_TABLE_PERM_NOCACHE | (long)pt1;
for(int i = 0; i < 512; i++) //Maps first GB of physical addresses to themselves in 2MB pages
{
pd1[i] = PAGE_TABLE_PERM_NOCACHE | PAGE_TABLE_LARGE_FLAG | (0x200000*i);
}
Module* kernel_mod = multiboot->Modules.Address; //Kernel is the first module loaded
ElfHeader* kernel = kernel_mod->Start;
/*if(!((kernel->Identification[1]) == 'E' && (kernel->Identification[2] == 'L') && (kernel->Identification[3] == 'F'))); //Check ELF signature
return 1;*/
if(kernel->Identification[4] != 2) //Check Elf Class (make sure its 64-bit)
return 2;
if(kernel->Identification[5] != 1) //Check Edianess (make sure its little)
return 3;
if(kernel->Type != 2) //Check File Type (make sure its executable)
return 4;
*entrypoint = kernel->Entry;
void* currentKernelBuffer = kernel_buffer;
for(int i = 0; i < kernel->NumberProgramHeaderEntry; i++) //Loop through the program headers and do what they say
{
ElfProgramHeader* program = (void*)kernel+((long)kernel->ProgramHeaderTableOffset)+(i*((long)kernel->ProgramHeaderEntrySize)); //get the program header
memcpy(currentKernelBuffer, (void*)kernel+program->Offset, 1, (int)program->FileSize); //Copy the data
for(unsigned long long j = 0; j <= program->MemSize; j += 4096) //Write page table entries for all the pages
{
pt1[(program->VAddr+j)/4096] = PAGE_TABLE_PERM_NOCACHE | (((unsigned long long)currentKernelBuffer)+j); //Write necessary page table entries
}
currentKernelBuffer += program->MemSize; //Increment buffer address
currentKernelBuffer = align(currentKernelBuffer);
}
return 0;
}
multiboot-elf32.ld: Linker Script:
Code: Select all
OUTPUT_FORMAT(elf32-i386)
ENTRY (loader)
SECTIONS{
. = 0x00100000;
.text ALIGN(4096) :{
*(.text)
}
.rodata ALIGN (4096) : {
*(.rodata)
}
.data ALIGN (4096) : {
_start_code16 = .;
*(.data)
_end_code16 = .;
}
.bss ALIGN (4096) : {
sbss = .;
*(COMMON)
*(.bss)
ebss = .;
}
}
And finally, the makefile:
Code: Select all
CC = gcc
CFLAGS = -m32 -std=gnu99 -nostartfiles -nostdlibs -nodefaultlibs
ASM = yasm
ASMFLAGS = -f elf32
LD = ld
LDFLAGS = -melf_i386 -T multiboot-elf32.ld -nostdlibs -nodefaultlibs
OUTPUT = bootloader
.PHONY : all clean
all: $(OUTPUT)
clean:
rm -f main.o
rm -f main16.bin
$(OUTPUT): main.o c.o
$(LD) $(LDFLAGS) -o $(OUTPUT) main.o c.o
main.o : main.s main16.bin
$(ASM) $(ASMFLAGS) -o main.o main.s
c.o : c.c
$(CC) $(CFLAGS) -c -o c.o c.c
main16.bin : main16.s
$(ASM) -f bin -o main16.bin main16.s
main16.s :
main.s :
c.c:
Put these in a directory, run make, and you should get an elf32 image "bootloader".
Write this in your grub legacy menu.lst:
Code: Select all
Title=OS Name
kernel /wherever/you/put/it/bootloader
module /wherever/you/put/it/kernel
A few notes about this code:
- It expects that the elf64 file (your real kernel) is the first multiboot module when loaded by grub
- It assumes the kernel wants to be loaded at virtual address 0xFFFF800000000000. This is the higher half of an Intel 64 canonical address. You can change this, but it will require messing with the page table generation
- The page tables map the first GB of physical addresses to themselves by the time the kernel is loaded
- This code assumes a 2MB or less kernel. My OS is a microkernel, so I won't even approach this number. However, if this becomes too small for your kernel, I imagine you will have enough experience to rewrite the page generation to accommodate a larger kernel. Its not a very big deal.
- You NEED to use yasm to assemble. Nasm or gas won't work (Nasm and elf64 = FAIL)
- If you need a linker script for your 64-bit kernel, just use the one provided by the 64-bit os tutorial on the wiki and use 0xFFFF800000000000 as the virtual address
If you have any questions feel free to ask. Also, if you need some code for kernel startup (like setting up kernel memory management, a new GDT, etc.) that is compatible with this loader, I can give you the code from my OS.