Page 1 of 1

Loading ELF file into memory

Posted: Sat Nov 17, 2012 11:51 pm
by zhiayang
1. I know there is a topic right before mine that seems to address the same basic concept, but I don't wish to intrude.
2. I know that I have a topic previously pertaining to such a topic, but I don't wish to necropost either.

Now then; Using this code,

Code: Select all

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);
			}
		}
	}
Which I have posted before, I copy each section to memory. Theoretically. Is this all I need to do before executing the file? I think so.

But anyway, tests have shown that ElfProgramHeaderEntries is 0. That code never gets executed, and nothing gets copied.
However, if I jump to the entry point anyway (I'm not actually doing that yet, but I need to know if I copied the file properly), it works, but is all kinds of screwed (presumably due to a lack of a 64-bit GDT). (Although it clears the screen as expected, I suspect all kinds of memory errors)

Here's my point: I'm pretty sure there are supposed to be more than 0 ProgramHeaders. I over-verify the ELF file (4 times), including before copying it to memory (it's in the way of the GRUB memory map) and after copying it. The header is there.

This is my ELF file:

start.s

Code: Select all

[BITS 64]
section .text
global Execute
extern main

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

	cli
	call main
	hlt

main.c

Code: Select all

void main()
{
	//unsigned int i = 6 / 0;
	unsigned char *videoram = (unsigned char*)0xB8000;
	unsigned int f = 0;
	for(int i = 0; i < 4001; i++)
	{
		videoram[i] = (char)(32);
	 	videoram[i + 1] = 0x0D;
	 	i++;
	}
}
link64.ld

Code: Select all

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

phys = 0x00200000;

SECTIONS
{
	.text phys :
	{
		code = .;
		*(.text)
		. = ALIGN(4096);
	}
	.data :
	{
		data = .;
		*(.data)
		*(.rodata)
		. = ALIGN(4096);
	}
	.bss :
	{
		bss = .;
		*(.bss)
		. = ALIGN(4096);
	}
	end = .; _end = .; __end = .;
}
OBJDUMP of kernel.bin:

Code: Select all

kernel.bin:     file format elf64-x86-64
kernel.bin
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0000000000200000

Program Header:
    LOAD off    0x0000000000200000 vaddr 0x0000000000200000 paddr 0x0000000000200000 align 2**21
         filesz 0x0000000000002000 memsz 0x0000000000002000 flags rwx

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00001000  0000000000200000  0000000000200000  00200000  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .eh_frame     00000030  0000000000201000  0000000000201000  00201000  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .data         00000fd0  0000000000201030  0000000000201030  00201030  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .comment      00000011  0000000000000000  0000000000000000  00202000  2**0
                  CONTENTS, READONLY
As can be observed, there IS a program header. So, why doesn't my code detect it? Help?

Thanks.

Re: Loading ELF file into memory

Posted: Sun Nov 18, 2012 1:46 am
by bluemoon

Code: Select all

for(unsigned int *i = 0; (unsigned int)i < KernelHeader->ElfSectionHeaderEntrySize * KernelHeader->ElfProgramHeaderEntries; i += KernelHeader->ElfSectionHeaderEntrySize)
issue#1: i is increased by ElfSectionHeaderEntrySize * sizeof(unsigned int) per iteration

issue#2: You seems mixed program header with section header, but they use different fields. FYI my load routine is something like 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;

            // register this segment to avoid collision, return a translated, usable memory address with the requested size
            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;
        }
    }
    relocate ( buffer, imports, imports_count );
    entry = translate_addr(elf_ehdr->e_entry);
requimrar wrote:Which I have posted before, I copy each section to memory. Theoretically. Is this all I need to do before executing the file? I think so.
This is the minimum thing to do to load a static linked file, but I'm sure there can be much more can be done things like validation, relocation, external function(shared object), address space randomization, etc for a more general case.
requimrar wrote:But anyway, tests have shown that ElfProgramHeaderEntries is 0. That code never gets executed, and nothing gets copied.
sound like a bug elsewhere. By the way have you pack your structure?

Re: Loading ELF file into memory

Posted: Sun Nov 18, 2012 10:25 am
by zhiayang
bluemoon wrote: issue#1: i is increased by ElfSectionHeaderEntrySize * sizeof(unsigned int) per iteration
Indeed, I appear to have misread the ELF specification. However, I think I was using this because it said somewhere that this would give me the number of bytes in the section. I don't quite get myself either...
issue#2: You seems mixed program header with section header, but they use different fields. FYI my load routine is something like this:

Code: Select all

    --SNIP--
            seg = add_segment((uintptr_t)elf_phdr->p_vaddr, (uintptr_t)elf_phdr->p_vaddr, (((size_t)elf_phdr->p_memsz + 4095)>>12)<<12 );
            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;
        --SNIP--
    relocate ( buffer, imports, imports_count );
    entry = translate_addr(elf_ehdr->e_entry);
1. I have code along these lines at the end of my file, that I did not show. However...
a. What is seg? I assume it means "Segment". As I understand it, the spec says that the Program Header Table describes program headers, and Program Headers simply describe segments. Using readelf,

Code: Select all

 Section to Segment mapping:
  Segment Sections...
   00     .text .eh_frame .data 
In that case, since I am already copying the segment(s), I need not copy each section individually, is that right?

b. What is seg->base? I don't find that in the ELF64 spec, and the ELF32 spec (1998) is quite vague about what exactly this does. Is this required for loading static files?

c. What does 'addsegment()' do? Is it and 'MMU_*' just some functions to interact with your MM?
d. I'm assuming I don't need to relocate anything with a static file.
e. What does 'translate_addr()' do?

And lastly, both objdump and readelf tell me that I have *ONE* program header. Why does my code insist on telling me I have 0?

Also: I did not pack my ELF structures, because using __attribute__((packed)) tells me that the attribute will be ignored. (GCC 4.7.1)
Also 2: Even if such code is not executed...

I copy the module to 0x0010400 (Just a free region of memory) and the entry point of the kernel is 0x0020000. If I just jump to 0x0020000, with nothing copied to the right address... it appears to execute... somewhat. Is this supposed to happen? (I'm betting absolutely not, and there's something wrong with something, or everything, in my loading routines)


If any overly-kind soul wishes to spend time helping, have a gander: here.

Re: Loading ELF file into memory

Posted: Sun Nov 18, 2012 11:19 am
by MDenham
requimrar wrote:
bluemoon wrote: issue#1: i is increased by ElfSectionHeaderEntrySize * sizeof(unsigned int) per iteration
Indeed, I appear to have misread the ELF specification. However, I think I was using this because it said somewhere that this would give me the number of bytes in the section. I don't quite get myself either...
No, it's just a case of how pointer math works in C. Aside from void*, adding/subtracting from pointers is scaled by the size of the type it's a pointer to. (void* is supposed to be byte-aligned instead, therefore no scaling. At least, to the best of my knowledge.)

So you've basically got two options:
1) Change i to be void*, and cast it back to (unsigned int*) as needed; or
2) Divide the header entry size by sizeof(unsigned int), preferably before starting the loop (to avoid unnecessary calculations).

Re: Loading ELF file into memory

Posted: Sun Nov 18, 2012 11:45 am
by bluemoon
Forgive me for not describe that more clear and make confusion.

Those segments in my code are things introduced by my loader that keep track of the entries of "program header", and for shared object it also keep track of the actual mapped address, which is different with the vaddr specified in program header - these information will be used for relocation later.
You may skip that part, or implement simular features with different methods.
requimrar wrote:Also: I did not pack my ELF structures, because using __attribute__((packed)) tells me that the attribute will be ignored. (GCC 4.7.1)
That's the problem. The elf structures are not in native word size.
are you sure you used __attribute__((packed)) correctly? it should look like this:

Code: Select all

typedef struct {
    ELF64_Addr      r_offset;
    ELF64_Xword     r_info;    
} __attribute__ ((__packed__)) ELF64_Rel;
I'm using gcc 4.6.3 which works fine, I'm not so sure about 4.7.1 but you may also try the #pragma pack(push,1) and #pragma pack(pop) introduced in recent versions of gcc for vc compatibility.

Re: Loading ELF file into memory

Posted: Mon Nov 19, 2012 8:08 pm
by zhiayang
bluemoon wrote:Forgive me for not describe that more clear and make confusion.
No worries, in fact that confusion prompted me to read the spec again, and fix my ELF file loading routine, which is appended below, for future explorers coming this way.
bluemoon wrote: That's the problem. The elf structures are not in native word size.
are you sure you used __attribute__((packed)) correctly? it should look like this:

--SNIP--

1. Indeed, I was putting __attribute__ ((__packed__)) after the semicolon on my struct declaration. Silly me.
2. However, the problem was not the unpacked structure, rather (thanks to klange and thePowersGang for pointing out), I was using 'unsigned long' etc on a 64-bit ELF file, when I should have been using <stdint.h>. Thanks guys!


Also: Could somebody help me check the code below? It works as expected, but I don't know what to expect, given that it's a 64-bit elf file in a 32-bit environment without paging.


Program Loading Code:

Code: Select all


	// 6.1.2: I am aware that such paranoia is probably unhealthy. Either way, check again.
	// VerifyElfHeader() Does exactly that; refer to ELF spec.

	if(!VerifyElfHeader((TElf64Header*)KernelModuleAddress))
	{
		HaltSystem();
	}


	// 6.2. Bring the ELF file into memory.


	TElf64Header *Kernel64 = (TElf64Header*)KernelModuleAddress;


	// 6.2.1: Why do I do this? I might need help.

	if(!VerifyElfHeader(Kernel64))
	{
		puts("\n\nInvalid ELF File\n\n");
		HaltSystem();
	}

	unsigned long ElfProgramHeaderOffset = Kernel64->ElfProgramHeaderOffset;
	void *ElfEntry = (void*)Kernel64->ElfEntry;


	// 6.2.2: Find and copy each program segment into memory.

	// Map: ElfHeader --> ProgramHeaderTable --> ProgramHeader --> Describes Segment.

	// In this case, we are dealing with a statically linked file. No relocations, no linking, no shared objects, nothing. Just load and jump.
	// Also, since all the sections are contained within this segment (checked with readelf), we don't need to copy them separately.


    TElf64ProgramHeader *ElfProgramHeader;			// Elf Program Header

	for(unsigned int i = 0; i < Kernel64->ElfProgramHeaderEntries; i++)
	{
		// 6.3.1: Set the current program header to the address of the offset from the beginning of the ELF file.
		ElfProgramHeader = (TElf64ProgramHeader*)(ElfProgramHeaderOffset + (unsigned int)Kernel64);

		// 6.3.2: Increase the offset, such that the next segment we load (if any) will be at the right place. Program headers are the same size.
		ElfProgramHeaderOffset += sizeof(*ElfProgramHeader);

		// 6.3.3: I can assure everyone that this is not paranoia: We need to check if the ELF file is actually loadable.
		if(ElfProgramHeader->ProgramType != ProgramTypeLoadableSegment)
		{
			puts("\n\n");
			TextModeSetTextColour(LIGHTBROWN);
			puts("ELF File not of Loadable type!");
			puts("\n");
			TextModeSetTextColour(WHITE);
			puts("Expected 1, was ");
			putnum(ElfProgramHeader->ProgramType);
			HaltSystem();
		}

		// 6.3.4: Copy the segment over. We use memmove simply because it handles overlapping memory regions.
		memmove((void*)(ElfProgramHeader->ProgramVirtualAddress), (void*)(ElfProgramHeader->ProgramOffset + Kernel64), ElfProgramHeader->ProgramFileSize);

		// 6.3.5: In the event that the program's memory is more than its file size, zero that region, so it doesn't have to deal with random data.
		memset((char*)(ElfProgramHeader->ProgramVirtualAddress + ElfProgramHeader->ProgramFileSize), 0, ElfProgramHeader->ProgramMemorySize - ElfProgramHeader->ProgramFileSize);


		// Once more, from the top! (Doesn't apply here)
	}

Re: Loading ELF file into memory

Posted: Mon Nov 19, 2012 8:22 pm
by SparrowOS
By the way, 64-bit only works with paging enabled. They did not give an option of no-paging in 64-bit mode or I would have used it.

I think auto-aligning a structure is silly -- my compiler does not autoalign.

Re: Loading ELF file into memory

Posted: Mon Nov 19, 2012 8:27 pm
by zhiayang
SparrowOS wrote:By the way, 64-bit only works with paging enabled. They did not give an option of no-paging in 64-bit mode or I would have used it.
I know. This is simply a reassurance to myself that my ELF loading code works. I am in no way planning to leave it at that stage.

Re: Loading ELF file into memory

Posted: Mon Nov 19, 2012 8:39 pm
by SparrowOS
Curiously, I have never done ELF loading. It started as a TASM DOS application. It changed from real-mode DOS to protected mode and never changed back. I wrote boot-loader and compiler and assembler. I have never used any compiler but my own and have never had elf files. I changed to long mode eventually.

Re: Loading ELF file into memory

Posted: Tue Nov 20, 2012 2:36 am
by bluemoon

Code: Select all

// 6.3.2: Increase the offset, such that the next segment we load (if any) will be at the right place. Program headers are the same size.
      ElfProgramHeaderOffset += sizeof(*ElfProgramHeader);
You should use the field in ELF header for forward compatibility.

Code: Select all

 if(ElfProgramHeader->ProgramType != ProgramTypeLoadableSegment) {
  ... 
     HaltSystem();
 }
While this might work in a particular file, there can be multiple program entry and not all of them are has PT_LOAD flag, example is PT_NULL. With such file your loader will go to halt.

Bottle line, you may want to add more validations (say, ElfProgramHeader->ProgramMemorySize - ElfProgramHeader->ProgramFileSize don't get you negative numbers). But overall the logic is good enough to load a static linked elf. The next step is to prepare the environment (bss, register state, etc), good luck.

Re: Loading ELF file into memory

Posted: Tue Nov 20, 2012 9:15 am
by zhiayang
blue moon wrote: You should use the field in ELF header for forward compatibility.

Code: Select all

	Elf64_Half			ElfProgramHeaderEntrySize; 
This, then?
bluemoon wrote: While this might work in a particular file, there can be multiple program entry and not all of them are has PT_LOAD flag, example is PT_NULL. With such file your loader will go to halt.

Bottle line, you may want to add more validations (say, ElfProgramHeader->ProgramMemorySize - ElfProgramHeader->ProgramFileSize don't get you negative numbers). But overall the logic is good enough to load a static linked elf. The next step is to prepare the environment (bss, register state, etc), good luck.
Well... I'm sure I can figure something out for actually loading programs, but for now my kernel fits the parameters. Thanks!