Grub2 (1.96) and ELF64 Kernel.

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
whowhatwhere
Member
Member
Posts: 199
Joined: Sat Jun 28, 2008 6:44 pm

Grub2 (1.96) and ELF64 Kernel.

Post by whowhatwhere »

I've got a 'fake' hard disk image formatted as ext2 with Grub2 (1.96) installed on it. However I am receiving an error upon boot. (I don't believe this is a bug, and I need grub2 in order to load elf64 files w/o bootstrapping.)

Screenshot:
Image

I'm about to start scanning through the grub2 sources, but I thought I'd ask here in case anyone knows what's going wrong.

I have a feeling my linker script may be the cause:

Code: Select all

OUTPUT_ARCH(i386:x86-64)
OUTPUT_FORMAT(elf64-x86-64)
ENTRY(kmain)

SECTIONS
{
    . = 0x00100000;

    .mbheader SIZEOF_HEADERS : AT(ADDR(.mbheader) - 0x00100000)
    {
        *(.mbheader)
        . = ALIGN(0x1000);
    }

    .text : AT(ADDR(.text) - 0x00100000)
    {
        _code = .;
        *(.text)
        *(.rodata*)
        . = ALIGN(0x1000);
    }

   .data : AT(ADDR(.data) - 0x00100000)
   {
        _data = .;
        *(.data)
        . = ALIGN(0x1000);
   }

   .ehframe : AT(ADDR(.ehframe) - 0x00100000)
   {
       _ehframe = .;
       *(.ehframe)
        . = ALIGN(0x1000);
   }

   .bss : AT(ADDR(.bss) - 0x00100000)
   {
       _bss = .;
       *(.bss)
       . = ALIGN(0x1000);
   }

   _end = .;

   /DISCARD/ :
   {
        *(.comment)
   }
}
Under x86_64, the mbheader is placed above file offset 0x2000, so it's located above 8K. I had to force it lower via this linker script, which places the section ".mbheader" exactly at SIZEOF_HEADERS, which I assumed was the lowest possible address w/o corrupting the ELF binary.
JohnnyTheDon
Member
Member
Posts: 524
Joined: Sun Nov 09, 2008 2:55 am
Location: Pennsylvania, USA

Re: Grub2 (1.96) and ELF64 Kernel.

Post by JohnnyTheDon »

I'm not sure if this is your problem, but where is your 32-bit bootloading code? You will need some assembly in your kernel image to go from protected mode to long mode. AFAIK, grub2 doesn't do this for you (though it does recognize the ELF64 format). Check out the 64-bit kernel tutorial if you haven't already (it covers using grub2 as well as grub legacy).

You may also want to consider avoiding grub2 (I found it to be quite a pain) and just use the 32-bit loader as your kernel image, then load your 64-bit kernel as a multiboot module, and then load the module and run it. I have done this successfully with grub legacy. If you decide to go this route, I have some good code that will do this (It also copies the grub multiboot info into a place your kernel can expect it, and goes back to real mode to change video modes).
whowhatwhere
Member
Member
Posts: 199
Joined: Sat Jun 28, 2008 6:44 pm

Re: Grub2 (1.96) and ELF64 Kernel.

Post by whowhatwhere »

JohnnyTheDon wrote:I'm not sure if this is your problem, but where is your 32-bit bootloading code? You will need some assembly in your kernel image to go from protected mode to long mode. AFAIK, grub2 doesn't do this for you (though it does recognize the ELF64 format). Check out the 64-bit kernel tutorial if you haven't already (it covers using grub2 as well as grub legacy).

You may also want to consider avoiding grub2 (I found it to be quite a pain) and just use the 32-bit loader as your kernel image, then load your 64-bit kernel as a multiboot module, and then load the module and run it. I have done this successfully with grub legacy. If you decide to go this route, I have some good code that will do this (It also copies the grub multiboot info into a place your kernel can expect it, and goes back to real mode to change video modes).
After going through Grub2's code, I realized that they claim to do a lot fo things that they don't yet even implement. Further analysis of the code disgusted me, and I promptly stopped using Grub2.

Your approach is what I tried next, but my developments into my operating system have strayed and I gave up after Grub legacy didn't even report things correctly either. I got fed up and placed the project on the back burner, as I had other matters in real life to deal with. I haven't touched it since then, and that was almost four months ago.

If you have some working code that you would be willing to share, it would be greatly appreciated for the sake of figuring out where I was going wrong, but it may be a long time before I actually bother doing anything with my project. I've got quite a bit to do nowadays, and none of it involves programming. *shrug*
JohnnyTheDon
Member
Member
Posts: 524
Joined: Sun Nov 09, 2008 2:55 am
Location: Pennsylvania, USA

Re: Grub2 (1.96) and ELF64 Kernel.

Post by JohnnyTheDon »

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.
whowhatwhere
Member
Member
Posts: 199
Joined: Sat Jun 28, 2008 6:44 pm

Re: Grub2 (1.96) and ELF64 Kernel.

Post by whowhatwhere »

JohnnyTheDon wrote:No problem. The code has 3 parts, a linker script, and a makefile:
...[snip]...
Thanks, I will see later on about reading into everything here, and maybe I can allocate some time in the next few months for working on Kairos again.
Post Reply