Loading ELF executables, 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
User avatar
alethiophile
Member
Member
Posts: 90
Joined: Sat May 30, 2009 10:28 am

Loading ELF executables, kernel

Post by alethiophile »

I am currently thinking that I want my kernel to be in ELF format. I am writing my own bootloader. How do I load the ELF executable? I read in the Rolling your Own BootLoader article that the kernel should be loaded at the location that is compiled/linked into its executable. What does that mean? I read the 'Loading' section of the ELF article, but it is not particularly helpful.
If I had an OS, there would be a link here.
User avatar
NickJohnson
Member
Member
Posts: 1249
Joined: Tue Mar 24, 2009 8:11 pm
Location: Sunnyvale, California

Re: Loading ELF executables, kernel

Post by NickJohnson »

It usually isn't too hard. Try looking at the ELF spec, but only focus on the loading section: http://www.skyfree.org/linux/references/ELF_Format.pdf

I have a partial ELF loader that is used only once during my kernel's bootup process (so it's kind of crappy and I don't care), but it may be useful as a model:

Code: Select all

#ifndef ELF_H
#define ELF_H

#include <lib.h>

// ELF Header
typedef struct {
	u8int e_ident[16];
	u16int e_type, e_machine;
	u32int e_version;
	u32int e_entry, e_phoff, e_shoff, e_flags;
	u16int e_ehsize, e_phentsize, e_phnum;
	u16int e_shentsize, e_shnum, e_shstrndx;
} __attribute__ ((packed)) elf_t;

#define ET_EXEC 2
#define EM_386 3

// ELF Program Header
typedef struct {
	u32int p_type, p_offset, p_vaddr, p_paddr;
	u32int p_filesz, p_memsz, p_flags, p_align;
} __attribute__ ((packed)) elf_ph_t;

#define PT_NULL 	0
#define PT_LOAD 	1
#define PT_DYNAMIC 	2
#define PT_INTERP 	3
#define PT_NOTE 	4
#define PT_SHLIB 	5
#define PT_PHDR		6

#define PF_R	0x1
#define PF_W	0x2
#define PF_X	0x4

void elf_load_segment(u8int *src, elf_ph_t *seg);
u32int elf_load(u8int *src); // Returns entry point
int elf_check(u8int *src);

#endif /*ELF_H*/

Code: Select all

#include <lib.h>
#include <elf.h>
#include <task.h>
#include <mem.h>

__attribute__ ((section(".ttext")))
void elf_load_segment(u8int *src, elf_ph_t *seg) {

	// Check if we can load this segment
	if (seg->p_type != PT_LOAD) return; // No libraries or any other crap!

	// Get pointer to source
	u8int *src_base = &src[seg->p_offset];

	// Get pointer to destination
	u8int *dest_base = (u8int*) seg->p_vaddr;
	u32int dest_limit = ((u32int) dest_base + seg->p_memsz + 0x1000) & ~0xFFF;

	// Allocate adequate memory
	task_t *t = get_task(curr_pid);
	map_load(&t->map);
	u32int i = ((u32int) dest_base) & ~0xFFF;
	for (; i < dest_limit; i += 0x1000)
		p_alloc(&t->map, i, PF_USER);

	// Copy data
	memcpy(dest_base, src_base, seg->p_memsz);

	// Set proper flags (i.e. remove write flag if needed)
	if (seg->p_flags & PF_W) {
		i = ((u32int) dest_base) & ~0xFFF;
		for (; i < dest_limit; i+= 0x1000)
			page_set(&t->map, i, page_fmt(page_get(&t->map, i), PF_USER | PF_PRES));
	}

}

__attribute__ ((section(".ttext")))
int elf_check(u8int *src) {
	elf_t *elf_header = (elf_t*) src;
	if (elf_header->e_ident[0] != 0x7F)return 1;
	if (elf_header->e_ident[1] != 'E') return 1;
	if (elf_header->e_ident[2] != 'L') return 1;
	if (elf_header->e_ident[3] != 'F') return 1;
	if (elf_header->e_type != ET_EXEC) return 1;
	if (elf_header->e_machine != EM_386) return 1;
	if (elf_header->e_version == 0) return 1;
	return 0;
}

__attribute__ ((section(".ttext")))
u32int elf_load(u8int *src) {
	u32int i, n;
	elf_t *elf_header = (elf_t*) src;

	// Load all segments
	elf_ph_t *program_header = (elf_ph_t*) &src[elf_header->e_phoff];
	n = elf_header->e_phnum; // Number of segments
	if (!n) return (u32int) NULL;

	for (i = 0; i < n; i++) elf_load_segment(src, &program_header[i]);
	
	return elf_header->e_entry; // Return entry point
}
You can remove the __attribute__ ((section(".ttext"))) if you want - it only pertains to my linker script and some special GC-like functions in my kernel. A lot of it is specific to my kernel, but most of the function calls should be self-explanatory enough.
User avatar
alethiophile
Member
Member
Posts: 90
Joined: Sat May 30, 2009 10:28 am

Re: Loading ELF executables, kernel

Post by alethiophile »

Thanks for this; the code is especially helpful. Do I need to worry about a .data segment? I've looked in the readelf of my kernel image and I don't see one, but my kernel image is just the 'Bare bones' version, so I'm not sure it's representative. What do you do to load a .data seg?
If I had an OS, there would be a link here.
User avatar
NickJohnson
Member
Member
Posts: 1249
Joined: Tue Mar 24, 2009 8:11 pm
Location: Sunnyvale, California

Re: Loading ELF executables, kernel

Post by NickJohnson »

The way the code is set up, it simply loops through the segments and loads all of them that are loadable. That includes both text and data. However, there is a block in there that sets everything to read-only (for reasons specific to my OS), so you would need to remove that to be able to actually *use* the .data segment. The only things you can't do with that code are shared/dynamic libraries and interpreters. It also only works for the x86. Of course, you have to make some modifications to that code anyway, because everyone's internal kernel memory management is different. The whole idea was to be a model anyway. I'm assuming you're going to write your own version.

Edit: Also, if you're making a higher half kernel (which I would generally recommend), there may be some issue with the physical vs. virtual loading addresses. I don't know if the linker takes care of it, or if you actually have to use the "e_paddr" instead of the "e_vaddr" when loading. You could try both if using "e_vaddr" doesn't work (i.e. you write somewhere where there is no memory).
Post Reply