ELF - Executables

These are pretty simple to implement as the formatting is pretty open sources. There are 2 main Sections that you need to be able to read.

This is a simple elf sections which is defined as

struct ELF64Header {
	unsigned char id[16];
	uint16_t type;
	uint16_t machine;
	uint32_t version;
	uint64_t entry;
	uint64_t phOff;
	uint64_t shOff;
	uint32_t flags;
	uint16_t hdrSize;
	uint16_t phEntrySize;
	uint16_t phNum;
	uint16_t shEntrySize;
	uint16_t shNum;
	uint16_t shStrIndex;
} __attribute__((packed));

This defines required information such as the type of architecture (Support checker) and where the process entry is for starting it. Also the offsets of the Program headers

Program Headers

struct ELF64ProgramHeader {
	uint32_t type;
	uint32_t flags;
	uint64_t offset;
	uint64_t vaddr;
	uint64_t paddr;
	uint64_t fileSize;
	uint64_t memSize;
	uint64_t align;
} __attribute__((packed));

This is very important as it describes the sections of the program that are needed to be loaded into memory (vaddr) and where about they are in the elf file (paddr) and the specific sizes.

The type is the important factor in understanding this. There are 3 main types for this.

#define PT_NULL 0 // Ignore
#define PT_LOAD 1 // Defines that it needs to be loaded into memory

// GNU
#define PT_GNU_STACK 0x6474E551 // Defines the requirement of no execute bit or not.

Loading

In order to load the ELF, I use two loops. One loop is in order to allocate all the memory using the Program Headers. (We do this as if we did it while we load some memory issues occur as some memory isn't allocated.)

{
	for (uint16_t i = 0; i < elfHdr.phNum; i++) {
		ELF64ProgramHeader elfPHdr = *((ELF64ProgramHeader*)(elf + elfHdr.phOff + i * elfHdr.phEntrySize));

		if(elfPHdr.memSize == 0 || elfPHdr.type != PT_LOAD) continue;
		else { // Load the data anyways
			uint64_t size_alligned_down = elfPHdr.memSize + (elfPHdr.vaddr & PAGE_SIZE_4K);
			// Calculate page count
			uint64_t pages = (size_alligned_down + PAGE_SIZE_4K - 1) / PAGE_SIZE_4K;

			// Calculate VAddr
			uint64_t virtAddress = base + elfPHdr.vaddr - (elfPHdr.vaddr & 0xFFF); // Remove end bytes
			size_t flags = 0;
			flags |= (size_t)PAGE_PRESENT;
			flags |= (size_t)PAGE_USER;
			flags |= (size_t)PAGE_WRITABLE;

			// Allocate the region
			Drivers::Video::Print("ELF Allocator: Allocaing range of %x to %x\n",
				virtAddress, virtAddress + pages * PAGE_SIZE_4K);
			Paging::Region* r = proc->allocator->AllocateMemory(pages * PAGE_SIZE_4K, virtAddress, true, true, flags);
		}
	}
}

And the other stage is actually loading the data into memory, and to check if we need the No execute. We also use it to check if the process is static or dynamic and so if we need to load the linker as well for library loading.

{ // DATA Loader
	for (uint16_t i = 0; i < elfHdr.phNum; i++) {
		ELF64ProgramHeader elfPHdr = *((ELF64ProgramHeader*)(elf + elfHdr.phOff + i * elfHdr.phEntrySize));

		Drivers::Video::Print("Program header: type=%x, offset=%x filesz=%x, memsz=%x\n",
			elfPHdr.type, elfPHdr.offset, elfPHdr.fileSize, elfPHdr.memSize);

		if(elfPHdr.memSize == 0) Drivers::Video::Print("    ! Empty PHdr\n");
		else { // Load the data anyways
			// Calculate size
			uint64_t size_alligned_down = elfPHdr.memSize + (elfPHdr.vaddr & PAGE_SIZE_4K);
			// Calculate page count
			uint64_t pages = (size_alligned_down + PAGE_SIZE_4K - 1) / PAGE_SIZE_4K;

			// Loading
			uint64_t virtAddress = base + elfPHdr.vaddr - (elfPHdr.vaddr & 0xFFF); // Remove end bytes
			uint64_t offset = elfPHdr.vaddr & 0xFFF; // Calculate its offset into the allocated memory
			Drivers::Video::Print("Memcpy data from %x into %x of size %x\n",
				elf + elfPHdr.offset, (uint8_t*)(virtAddress + offset), elfPHdr.fileSize);
			asm volatile("cli; mov %%rax, %%cr3" ::"a"(proc->GetPageMap()->pml4Phys) : "memory");
			memcpy((uint8_t*)(virtAddress + offset), elf + elfPHdr.offset, elfPHdr.fileSize);
			asm volatile("mov %%rax, %%cr3; sti" ::"a"(pml4Phys) : "memory");

			Drivers::Video::Print("    Loaded program header (%x Bytes) at offset %x into %x\n",
				elfPHdr.fileSize, elfPHdr.offset, base + elfPHdr.vaddr);
		}
		if (elfPHdr.type == PT_PHDR) {
			Drivers::Video::Print("    ! PHDR found, saving address: %x!\n", elfPHdr.vaddr);
			elfInfo.pHdrSegment = base + elfPHdr.vaddr;
		}
		else if (elfPHdr.type == PT_GNU_STACK){                
			Drivers::Video::Print("    ! Stack permission set by GNU_STACK Program header!: %Y\n", !(elfPHdr.flags & PF_X));
			if (!(elfPHdr.flags & PF_X))
				stack_flags |= PAGE_NX;
		} else if (elfPHdr.type == PT_INTERP) { // Linker location
			linkPath = (char*)Memory::Heap::malloc(elfPHdr.fileSize + 1);
			strncpy(linkPath, (char*)(elf + elfPHdr.offset), elfPHdr.fileSize);
			linkPath[elfPHdr.fileSize] = 0; // Null terminate the path
			elfInfo.linkerPath = linkPath;

			Drivers::Video::Print("    ! Linker path found: %s!\n", linkPath);
		}
	}
}

Last updated