Page 1 of 2

Entry point of ELF File at 0x00000000!

Posted: Fri Aug 31, 2012 9:50 am
by zhiayang
Welp. I'm trying to load my kernel ELF file now, passed to GRUB as a module. A problem: Using this:

Code: Select all

        KernelModule = (module_t*)MBT->mods_addr;

	unsigned char *temp = (unsigned char*)KernelModule->mod_start;

	// 2. Verify the ELF header:

	puts("Loading kernel module from ");
	puthex(KernelModule->mod_start);
	puts(" of length ");
	puthex(KernelModule->mod_end - KernelModule->mod_start);
	puts("\n");

	// 2.1: Check the header: [0] should be 0x7F, [1] E, [2] L, [3] F.

	if(!(temp[0] == ELF_MAGIC0 && temp[1] == ELF_MAGIC1 && temp[2] == ELF_MAGIC2 && temp[3] == ELF_MAGIC3))
	{
		-snip-
	}

	// 2.2: If we got here, the kernel *should* be usable.
	// Since the ELF Magic thingy is there, we can assume that the program header exists here:

	KernelHeader = (TElf64Header*)KernelModule->mod_start;

	// 2.3: Do some more checks, just in case:

	// Check ELF Version
	if(KernelHeader->ElfVersion != 1)
	{
		TextModeSetTextColour(LIGHTRED);
		puts("ELF Kernel file was invalid: Expected ElfVersion to be 1, got ");
		putnum(KernelHeader->ElfVersion);
		HaltSystem();
	}

	// 2.4: Check if it's a 64-bit file:
	if(KernelHeader->ElfIdentification[4] != 2)
	{
		TextModeSetTextColour(LIGHTRED);
		puts("ELF Kernel was invalid: Expcted ElfIdentification[4] (ElfClass) to be 2 (64-bit file), got ");
		putnum(KernelHeader->ElfIdentification[4]);
		HaltSystem();
	}


	// 3: Begin loading ELF File into memory.
	// Load the loadable segments:
	for(unsigned int *i = 0; (unsigned int)i < KernelHeader->ElfSectionHeaderEntrySize * KernelHeader->ElfProgramHeaderEntries; i += KernelHeader->ElfSectionHeaderEntrySize)
	{
		// 3.1: Get ourselves a section header
		TElf64SectionHeader *SectionHeader = (TElf64SectionHeader*)((unsigned int)KernelHeader + (KernelHeader->ElfSectionHeaderOffset + i));
		if(SectionHeader->SectionHeaderAddress)
		{
			// 3.2: Check for BSS section:
			if(SectionHeader->SectionHeaderType == SectionHeaderTypeNoBits)
			{
				// This is the BSS section, zero it
				memset((void*)SectionHeader->SectionHeaderAddress, 0, SectionHeader->SectionHeaderSize);
			}
			else
			{
				// Copy it somewhere.
				memcpy((void*)SectionHeader->SectionHeaderAddress, (void*)((unsigned int)KernelHeader + SectionHeader->SectionHeaderOffset), SectionHeader->SectionHeaderSize);
			}
		}
	}

	// 3.2: Store the entry point.
	unsigned int *KernelEntryPoint = (unsigned int*)KernelHeader->ElfEntry;
	puthex(KernelHeader->ElfEntry);
This is my kernel (asm file, with NASM 64-bit) (for some reason, the CLI tools from Xcode only contained one which works for 32-bits)

Code: Select all

[BITS 64]
global Execute

Execute:
	; Indicate something to the outside world that we're here.

	cli
	hlt

.NoCPUID:
	cli
	hlt

.NoLongMode:
	cli
	hot
And this is my link64.ld:

Code: Select all

ENTRY(Execute)
OUTPUT_FORMAT(elf64-x86-64)

SECTIONS
{
	.text ALIGN(0x1000):
	{
		code = .;
		*(.text)
		*(.rodata)
		. = ALIGN(4096);
	}
	.rodata ALIGN(0x1000):
	{
		*(.rodata*)
		. = ALIGN(4096);
	}
	.data ALIGN(0x1000):
	{
		data = .;
		*(.data)
		. = ALIGN(4096);
	}
	.bss ALIGN(0x1000):
	{
		bss = .;
		*(.bss)
		. = ALIGN(4096);
	}
	end = .; _end = .; __end = .;
}
Sorry for the code vomit, but that's basically what I'm using to
1. Verify the ELF file (I get no errors whatsoever)
2. Load it into memory

Basically, using the C code, I copy the things over to memory. Problem is, the entry address is 0x00000000. Which shouldn't be the case.
Also: This is the file start and size given by GRUB:
0x0010F000, 0x002012E3

I don't initialise my PMM before doing this, because i'm afraid it will ruin my GRUB structures.

What's the situation?

Also some other questions:

1. Do I need to set up a 64-bit IDT and GDT before or after entering long mode?
2. *how* do I enter long mode? I understand that I should set a certain bit in a certain register, but then what? How do I jump to the entry address (presuming I get the above problem resolved)?


EDIT: Also, I'm using

Code: Select all

nasm -f elf64 -o start.o src64/asm/start.s
Cross64/bin/x86_64-elf-ld -T link64.ld -o output/kernel.bin start.o
to get my kernel.bin.

Re: Entry point of ELF File at 0x00000000!

Posted: Fri Aug 31, 2012 11:12 pm
by jnc100
Problem is, the entry address is 0x00000000. Which shouldn't be the case.
Why not? If you use a custom linker script you need to specify where you want your sections to be (if you compare your linker script with the many in the wiki for the bare bones series you will see the problem). You can use readelf/x86_64-elf-objdump to verify the entry point of your module.
1. Do I need to set up a 64-bit IDT and GDT before or after entering long mode?
IDT - before or after (but before any interrupt/exception occurs). GDT - before.
2. *how* do I enter long mode? I understand that I should set a certain bit in a certain register, but then what? How do I jump to the entry address (presuming I get the above problem resolved)?
The process for entering long mode is well documented in the Intel manuals. After that, your question is essentially 'how do I jump to an address?' for which there is an assembly instruction. If you want to execute at a lower privilege level then see Getting to Ring 3.

Regards,
John.

Re: Entry point of ELF File at 0x00000000!

Posted: Sat Sep 01, 2012 12:10 am
by zhiayang
thanks for your reply, jnc100. Just a confirmation:

1. Because the ENTRY field of the linker script isn't an explicit address, it defaults to 0x00000000?
2. To jump to a specific address in c, I can cast the address to a function pointer and call that, right?


EDIT: If I were to specify the entry point explicitly in the linker...what if, in my memory map, that region is marked as reserved? what do I do then? do I try to move it…?

EDIT 2: Hmm… if I change my linker script to

Code: Select all

phys = 0x00200000;

SECTIONS
{
	.text ALIGN(0x1000) : AT(phys)
	{
		code = .;
		*(.text)
		*(.rodata)
		. = ALIGN(4096);
	}
	.rodata ALIGN(0x1000):
	{
		*(.rodata*)
		. = ALIGN(4096);
	}
	.data ALIGN(0x1000) : AT(phys + (data - code))
	{
		data = .;
		*(.data)
		. = ALIGN(4096);
	}
	.bss ALIGN(0x1000) : AT(phys + (bss - code))
	{
		bss = .;
		*(.bss)
		. = ALIGN(4096);
	}
	end = .; _end = .; __end = .;
}
….

Mainly forcing the placement of my sections somewhere… However, forcing it at 0x00100000 tells me that

Code: Select all

output/kernel.bin: Not enough room for program headers, try linking with -N
x86_64-elf-ld: final link failed: Bad value
so it's now 0x00200000.

Also: here's the output of readelf on the kernel file:

Code: Select all

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          2101288 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         1
  Size of section headers:           64 (bytes)
  Number of section headers:         5
  Section header string table index: 2

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00200000
       0000000000001000  0000000000000000  AX       0     0     16
  [ 2] .shstrtab         STRTAB           0000000000000000  00201000
       0000000000000021  0000000000000000           0     0     1
  [ 3] .symtab           SYMTAB           0000000000000000  00201168
       0000000000000138  0000000000000018           4     5     8
  [ 4] .strtab           STRTAB           0000000000000000  002012a0
       0000000000000060  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000200000 0x0000000000000000 0x0000000000200000
                 0x0000000000001000 0x0000000000001000  R E    200000

 Section to Segment mapping:
  Segment Sections...
   00     .text 

There is no dynamic section in this file.

There are no relocations in this file.

There are no unwind sections in this file.

Symbol table '.symtab' contains 13 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     2: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS src64/asm/start.s
     3: 0000000000000002     0 NOTYPE  LOCAL  DEFAULT    1 Execute.NoCPUID
     4: 0000000000000004     0 NOTYPE  LOCAL  DEFAULT    1 Execute.NoLongMode
     5: 0000000000200000     0 NOTYPE  GLOBAL DEFAULT  ABS phys
     6: 0000000000001000     0 NOTYPE  GLOBAL DEFAULT  ABS end
     7: 0000000000001000     0 NOTYPE  GLOBAL DEFAULT  ABS __end
     8: 0000000000001000     0 NOTYPE  GLOBAL DEFAULT    1 bss
     9: 0000000000001000     0 NOTYPE  GLOBAL DEFAULT    1 data
    10: 0000000000001000     0 NOTYPE  GLOBAL DEFAULT  ABS _end
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT    1 code
    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT    1 Execute

No version information found in this file.
It does indeed show the entry point as 0x0, but that doesn't seem right.

Re: Entry point of ELF File at 0x00000000!

Posted: Sat Sep 01, 2012 4:34 am
by xenos
Going to long mode is actually rather simple. Assuming that you are in protected mode with paging turned off,
  • Create some page tables for long mode. Make sure you are on an identity-mapped page.
  • Set CR3 to the address of the PML4T.
  • Enable long mode by setting the long mode enable bit in its MSR.
  • Activate long mode by enabling paging in CR0. Now you are in compatibility mode, since your code segment is still 32 bits.
  • Reload the GTDR with some GDT containing a 64 bit code segment (if you have not already done this before activating long mode).
  • Do a far jump to a 64 bit code segment.
Your entry address problem indeed looks a bit strange. Setting the entry address by ENTRY(Execute) should be fine. However, I wonder why Execute contains this address:

Code: Select all

12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT    1 Execute
It should be at some other address. Does NASM put everything into the .text section by default? What happens if you explicitly tell it to do so? (Should be section .text in NASM before Execute.)

This "not enough space for headers" problem happens when ld tries to place the ELF headers at some address where they collide with already existing structures. I encountered this problem once and solved it by defining an ELF segment (not section) in the program which explicitly contains the ELF file headers and starts at 0x100000. The .text / .data etc. sections follow after these headers. This is what it looks like in my linker script; see the PHDRS part. (Note that this is an upper half kernel.)

Re: Entry point of ELF File at 0x00000000!

Posted: Sat Sep 01, 2012 4:55 am
by bluemoon
requimrar wrote:Problem is, the entry address is 0x00000000.
This is because entry address is in VMA unit, not file offset nor PMA; and the address counter default starts at VMA=0.
This is my link script for 64-bit kernel for your reference. Note that the . = KERNEL_VMA; adjust the "starting point" of the code to some high address, and then I do a hack to skip 4096 byte which contain the ELF header - if you got a better solution welcome feedback to me.

Code: Select all

OUTPUT_FORMAT(elf64-x86-64)
ENTRY(bootstrap)


KERNEL_PMA = 0x00100000;
KERNEL_VMA = 0xFFFFFFFF80000000 + KERNEL_PMA +4096;


SECTIONS
{
    . = KERNEL_VMA;
    _kernel_start = .;
    . = ALIGN(4096);
    
    .text : AT(ADDR(.text) - KERNEL_VMA)
    {
        *bootstrap*(.text)
        *(.text)
        *(.gnu.linkonce.t*)
        . = ALIGN(4096);
    }
    
    .rodata : AT(ADDR(.rodata) - KERNEL_VMA)
    {
        ctor_start = .;
        *(.ctor*)
        ctor_end = .;
        dtor_start = .;
        *(.dtor*)
        dtor_end = .;

        *(.rodata*)
        *(.gnu.linkonce.r*)
        . = ALIGN(4096);
    }
    

    .data : AT(ADDR(.data) - KERNEL_VMA)
    {
        *(.data)
        *(.gnu.linkonce.d*)
        . = ALIGN(4096);
    }

    .bss : AT(ADDR(.bss) - KERNEL_VMA)
    {
        sbss = .;
        *(.bss)
        *(COMMON)
        *(.gnu.linkonce.b*)
        ebss = .;
        . = ALIGN(4096);
    }

    _kernel_end = .;

    /DISCARD/ :
    {
        *(.comment)
        *(.eh_frame*)
    }
}

Re: Entry point of ELF File at 0x00000000!

Posted: Sat Sep 01, 2012 5:49 am
by bluemoon
In your code:

Code: Select all

   // 3: Begin loading ELF File into memory.
   // Load the loadable segments:
   for(unsigned int *i = 0; (unsigned int)i < KernelHeader->ElfSectionHeaderEntrySize * KernelHeader->ElfProgramHeaderEntries; i += KernelHeader->ElfSectionHeaderEntrySize)
Your code may work, but I would like to propose an alternative (there is always more than one way to do this)
IMO it is simpler to scan the program header, I do this:

Code: Select all

    // Load program into vaddr
    // TODO: validate vaddr
    for(unsigned int i=0; i<(unsigned int)elf_ehdr->e_phnum; i++) {
        elf_phdr = (ELF64_Phdr*)(buffer + elf_ehdr->e_phoff + i * elf_ehdr->e_phentsize);
        switch (elf_phdr->p_type) {
        case PT_LOAD:
            if ( elf_phdr->p_memsz < elf_phdr->p_filesz ) goto fail;
            seg = add_segment((uintptr_t)elf_phdr->p_vaddr, (uintptr_t)elf_phdr->p_vaddr, (((size_t)elf_phdr->p_memsz + 4095)>>12)<<12 );
                
            MMU_mmap((void*)seg->base, 0, seg->size, MMU_PROT_RW|MMU_PROT_USER);
            memcpy ( (void*)seg->base, buffer + elf_phdr->p_offset, elf_phdr->p_filesz );
            if ( elf_phdr->p_filesz < elf_phdr->p_memsz ) {
                memset ( (void*)(seg->base + elf_phdr->p_filesz), 0, elf_phdr->p_memsz - elf_phdr->p_filesz);
            }
            break;
        case PT_NULL:
        case PT_DYNAMIC:
        case PT_INTERP:
        case PT_NOTE:
        case PT_SHLIB:
        case PT_PHDR:
            break;
        }
    }

Re: Entry point of ELF File at 0x00000000!

Posted: Sat Sep 01, 2012 10:24 am
by zhiayang
XenOS wrote:Going to long mode is actually rather simple. Assuming that you are in protected mode with paging turned off,
  • Create some page tables for long mode. Make sure you are on an identity-mapped page.
  • Set CR3 to the address of the PML4T.
  • Enable long mode by setting the long mode enable bit in its MSR.
  • Activate long mode by enabling paging in CR0. Now you are in compatibility mode, since your code segment is still 32 bits.
  • Reload the GTDR with some GDT containing a 64 bit code segment (if you have not already done this before activating long mode).
  • Do a far jump to a 64 bit code segment.
Your entry address problem indeed looks a bit strange. Setting the entry address by ENTRY(Execute) should be fine. However, I wonder why Execute contains this address:

Code: Select all

12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT    1 Execute
It should be at some other address. Does NASM put everything into the .text section by default? What happens if you explicitly tell it to do so? (Should be section .text in NASM before Execute.)

This "not enough space for headers" problem happens when ld tries to place the ELF headers at some address where they collide with already existing structures. I encountered this problem once and solved it by defining an ELF segment (not section) in the program which explicitly contains the ELF file headers and starts at 0x100000. The .text / .data etc. sections follow after these headers. This is what it looks like in my linker script; see the PHDRS part. (Note that this is an upper half kernel.)

I just tried; making the ASM file of section .text does nothing.

However,
blue moon wrote: This is because entry address is in VMA unit, not file offset nor PMA; and the address counter default starts at VMA=0.
This is my link script for 64-bit kernel for your reference. Note that the . = KERNEL_VMA; adjust the "starting point" of the code to some high address, and then I do a hack to skip 4096 byte which contain the ELF header - if you got a better solution welcome feedback to me.
Correct me if I'm wrong, but that statement seems to imply that I need to set up paging in order to properly load and jump to the ELF file.
If it's true, it sucks. I'm trying to set up a *minimal* environment; a simple PMM (which isn't done) and nothing else.
Also: I can't actually initialise the PMM before loading the kernel:

The PMM uses X bits from 0x00100000 onwards for a bitmap; this would be whatever's in the multi boot header.

Which means… I need the PMM to allocate some space to put the header; however, to initialise the PMM, I need to destroy some of it. That's just ghey.


But: *please* don't tell me I need a VMM. Please.

Re: Entry point of ELF File at 0x00000000!

Posted: Sat Sep 01, 2012 11:24 am
by bluemoon
requimrar wrote:Correct me if I'm wrong, but that statement seems to imply that I need to set up paging in order to properly load and jump to the ELF file.
No. The usual practice is write a position independent stub at the entry and enable paging. Moreover, if you plan to not use paging, that just mean VMA=PMA.

Re: Entry point of ELF File at 0x00000000!

Posted: Sat Sep 01, 2012 8:15 pm
by zhiayang
bluemoon wrote:
requimrar wrote:Correct me if I'm wrong, but that statement seems to imply that I need to set up paging in order to properly load and jump to the ELF file.
No. The usual practice is write a position independent stub at the entry and enable paging. Moreover, if you plan to not use paging, that just mean VMA=PMA.
Wait… If I don't plan to use paging, and VMA=PMA, then the PMA entry is 0x00000000. Is there something obvious that I cannot comprehend?

Re: Entry point of ELF File at 0x00000000!

Posted: Sat Sep 01, 2012 10:06 pm
by xenos
I guess your problem is with the AT directive for your sections, which sets the VMA to your desired address, but leaves the PMA at 0x0. If you want to set the PMA of your sections, instead of

Code: Select all

.text ALIGN(0x1000) : AT(phys)
it should be

Code: Select all

.text (phys)
The ALIGN is not necessary here, because phys is already aligned. If you don't specify the VMA explicitly using an AT directive, it should be the same as the PMA. For the remaining sections, you won't neet AT at all, so instead of

Code: Select all

.data ALIGN(0x1000) : AT(phys + (data - code))
you just need

Code: Select all

.data ALIGN(0x1000)
which will put .data at a page boundary and set PMA and VMA to the same value (which is equivalent to setting the VMA to phys + (data - code), since code = phys and data = PMA).

Re: Entry point of ELF File at 0x00000000!

Posted: Sat Sep 01, 2012 10:43 pm
by zhiayang
XenOS wrote:I guess your problem is with the AT directive for your sections, which sets the VMA to your desired address, but leaves the PMA at 0x0. If you want to set the PMA of your sections, instead of

Code: Select all

.text ALIGN(0x1000) : AT(phys)
it should be

Code: Select all

.text (phys)
The ALIGN is not necessary here, because phys is already aligned. If you don't specify the VMA explicitly using an AT directive, it should be the same as the PMA. For the remaining sections, you won't neet AT at all, so instead of

Code: Select all

.data ALIGN(0x1000) : AT(phys + (data - code))
you just need

Code: Select all

.data ALIGN(0x1000)
which will put .data at a page boundary and set PMA and VMA to the same value (which is equivalent to setting the VMA to phys + (data - code), since code = phys and data = PMA).

I love you in a completely non-homosexual manner. Seriously though, is there a good online reference for linker scripts, tailored specifically for OSDev?

Re: Entry point of ELF File at 0x00000000!

Posted: Sat Sep 01, 2012 11:40 pm
by zhiayang
Last few questions:

1. If I've jumped to my kernel compiled as a 64-bit code, but have yet to enable any of the bits, what mode am I in?
2. Do I *have* to enable paging *before* I move to long mode?
3. Also: there seems to be a lull of about 1 second before my code gets executed; I'm simply printing an 'A' in the upper right corner. Also: any CPU exceptions seem to be calling my 32-bit ISR code; this is normal, right?

Re: Entry point of ELF File at 0x00000000!

Posted: Sun Sep 02, 2012 1:55 am
by xenos
requimrar wrote:I love you in a completely non-homosexual manner. Seriously though, is there a good online reference for linker scripts, tailored specifically for OSDev?
I don't know whether there is one specifically for OSDev, but the ld documentation is pretty good and explains linker scripts rather well. I guess this is what's meant by "Know your tools" ;)
1. If I've jumped to my kernel compiled as a 64-bit code, but have yet to enable any of the bits, what mode am I in?
Assuming you come from GRUB, you are in protected mode. If you're coming from your own boot loader, you're in whatever state your boot loader establishes. If you're still in protected mode, you cannot execute 64 bit code immediately. You need some 32 bit stub that sets up and enters long mode first.
2. Do I *have* to enable paging *before* I move to long mode?
You have to set up all paging structures, but you must not enable paging before entering long mode. Activating long mode is done by enabling paging with the long mode enable bit set.
3. Also: there seems to be a lull of about 1 second before my code gets executed; I'm simply printing an 'A' in the upper right corner. Also: any CPU exceptions seem to be calling my 32-bit ISR code; this is normal, right?
Are you using some simulator or read hardware? Have you set up a 64 bit IDT?

Re: Entry point of ELF File at 0x00000000!

Posted: Sun Sep 02, 2012 8:20 am
by zhiayang
XenOS wrote: I don't know whether there is one specifically for OSDev, but the ld documentation is pretty good and explains linker scripts rather well. I guess this is what's meant by "Know your tools" ;)
Thanks for the link, I'll read up on that.
Assuming you come from GRUB, you are in protected mode. If you're coming from your own boot loader, you're in whatever state your boot loader establishes. If you're still in protected mode, you cannot execute 64 bit code immediately. You need some 32 bit stub that sets up and enters long mode first.
I think I just did… I don't know what defines '64-bit code', but I can execute code in a 64-bit elf by jumping directly to it. Probably a bad idea though.
You have to set up all paging structures, but you must not enable paging before entering long mode. Activating long mode is done by enabling paging with the long mode enable bit set.
Bah. I hate paging. (at least setting it up)
Are you using some simulator or read hardware? Have you set up a 64 bit IDT?
Yes, I'm using QEMU. Is that some kind of anomaly?

Re: Entry point of ELF File at 0x00000000!

Posted: Sun Sep 02, 2012 8:33 am
by bluemoon
requimrar wrote:Bah. I hate paging. (at least setting it up)
The more you hate it, the more dopamine you got when you master it. (And it's not that difficult to get it work)