/** * \brief ELF loading/unloading support * \author Sebastian Sumpf * \date 2014-10-26 */ /* * Copyright (C) 2014 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU General Public License version 2. */ #include #include #include #include #include char const *Linker::ELFMAG = "\177ELF"; using namespace Linker; using namespace Genode; namespace Linker { class Rm_area; struct Elf_file; } /** * Managed dataspace for ELF files (singelton) */ class Linker::Rm_area : public Rm_connection { private: /* size of dataspace */ enum { RESERVATION = 160 * 1024 * 1024 }; addr_t _base; /* base address of dataspace */ Allocator_avl _range; /* VM range allocator */ protected: Rm_area(addr_t base) : Rm_connection(0, RESERVATION), _range(env()->heap()) { on_destruction(KEEP_OPEN); _base = (addr_t) env()->rm_session()->attach_at(dataspace(), base); _range.add_range(base, RESERVATION); } public: static Rm_area *r(addr_t base = 0) { /* * The capabilities in this class become invalid when doing a * fork in the noux environment. Hence avoid destruction of * the singleton object as the destructor would try to access * the capabilities also in the forked process. */ static bool constructed = 0; static char placeholder[sizeof(Rm_area)]; if (!constructed) { construct_at(placeholder, base); constructed = 1; } return reinterpret_cast(placeholder); } /** * Reserve VM region of 'size' at 'vaddr'. Allocate any free region if * 'vaddr' is zero */ addr_t alloc_region(size_t size, addr_t vaddr = 0) { addr_t addr = vaddr; if (addr && (_range.alloc_addr(size, addr).is_error())) throw Region_conflict(); else if (!addr && _range.alloc_aligned(size, (void **)&addr, 12).is_error()) throw Region_conflict(); return addr; } void free_region(addr_t vaddr) { _range.free((void *)vaddr); } /** * Overwritten from 'Rm_connection' */ Local_addr attach_at(Dataspace_capability ds, addr_t local_addr, size_t size = 0, off_t offset = 0) { return Rm_connection::attach_at(ds, local_addr - _base, size, offset); } /** * Overwritten from 'Rm_connection' */ Local_addr attach_executable(Dataspace_capability ds, addr_t local_addr, size_t size = 0, off_t offset = 0) { return Rm_connection::attach_executable(ds, local_addr - _base, size, offset); } void detach(Local_addr local_addr) { Rm_connection::detach((addr_t)local_addr - _base); } }; /** * Map ELF files */ struct Linker::Elf_file : File { Rom_connection rom; Ram_dataspace_capability ram_cap[Phdr::MAX_PHDR]; bool loaded; Elf_file(char const *name, bool load = true) : rom(name), loaded(load) { load_phdr(); if (load) load_segments(); } virtual ~Elf_file() { if (loaded) unload_segments(); } /** * Check if ELF is sane */ bool check_compat(Elf::Ehdr const *ehdr) { if (memcmp(ehdr, ELFMAG, SELFMAG) != 0) { PERR("LD: binary is not an ELF"); return false; } if (ehdr->e_ident[EI_CLASS] != ELFCLASS) { PERR("LD: support for 32/64-bit objects only"); return false;; } return true; } /** * Copy program headers and read entry point */ void load_phdr() { Elf::Ehdr *ehdr = (Elf::Ehdr *)env()->rm_session()->attach(rom.dataspace(), 0x1000); if (!check_compat(ehdr)) throw Incompatible(); /* set entry point and program header information */ phdr.count = ehdr->e_phnum; entry = (Entry)ehdr->e_entry; /* copy program headers */ addr_t header = (addr_t)ehdr + ehdr->e_phoff; for (unsigned i = 0; i < phdr.count; i++, header += ehdr->e_phentsize) memcpy(&phdr.phdr[i], (void *)header, ehdr->e_phentsize); env()->rm_session()->detach(ehdr); Phdr p; loadable_segments(p); /* start vaddr */ start = trunc_page(p.phdr[0].p_vaddr); Elf::Phdr *ph = &p.phdr[p.count - 1]; /* size of lodable segments */ size = round_page(ph->p_vaddr + ph->p_memsz) - start; } /** * Find PT_LOAD segemnts */ void loadable_segments(Phdr &result) { for (unsigned i = 0; i < phdr.count; i++) { Elf::Phdr *ph = &phdr.phdr[i]; if (ph->p_type != PT_LOAD) continue; if (ph->p_align & (0x1000 - 1)) { PERR("LD: Unsupported alignment %p", (void *)ph->p_align); throw Incompatible(); } result.phdr[result.count++] = *ph; } } bool is_rx(Elf::Phdr const &ph) { return ((ph.p_flags & PF_MASK) == (PF_R | PF_X)); } bool is_rw(Elf::Phdr const &ph) { return ((ph.p_flags & PF_MASK) == (PF_R | PF_W)); } /** * Load PT_LOAD segments */ void load_segments() { Phdr p; /* search for PT_LOAD */ loadable_segments(p); /* allocate region */ reloc_base = Rm_area::r(start)->alloc_region(size, start); reloc_base = (start == reloc_base) ? 0 : reloc_base; if (verbose_loading) PDBG("reloc_base: " EFMT " start: " EFMT " end: " EFMT, reloc_base, start, reloc_base + start + size); for (unsigned i = 0; i < p.count; i++) { Elf::Phdr *ph = &p.phdr[i]; if (is_rx(*ph)) load_segment_rx(*ph); else if (is_rw(*ph)) load_segment_rw(*ph, i); else { PERR("LD: Non-RW/RX segment"); throw Invalid_file(); } } } /** * Map read-only segment */ void load_segment_rx(Elf::Phdr const &p) { Rm_area::r()->attach_executable(rom.dataspace(), trunc_page(p.p_vaddr) + reloc_base, round_page(p.p_memsz), trunc_page(p.p_offset)); } /** * Copy read-write segment */ void load_segment_rw(Elf::Phdr const &p, int nr) { void *src = env()->rm_session()->attach(rom.dataspace(), 0, p.p_offset); addr_t dst = p.p_vaddr + reloc_base; ram_cap[nr] = env()->ram_session()->alloc(p.p_memsz); Rm_area::r()->attach_at(ram_cap[nr], dst); memcpy((void*)dst, src, p.p_filesz); /* clear if file size < memory size */ if (p.p_filesz < p.p_memsz) memset((void *)(dst + p.p_filesz), 0, p.p_memsz - p.p_filesz); env()->rm_session()->detach(src); } /** * Unmap segements, RM regions, and free allocated dataspaces */ void unload_segments() { Phdr p; loadable_segments(p); /* detach from RM area */ for (unsigned i = 0; i < p.count; i++) Rm_area::r()->detach(trunc_page(p.phdr[i].p_vaddr) + reloc_base); /* free region from RM area */ Rm_area::r()->free_region(trunc_page(p.phdr[0].p_vaddr) + reloc_base); /* free ram of RW segments */ for (unsigned i = 0; i < Phdr::MAX_PHDR; i++) if (ram_cap[i].valid()) { env()->ram_session()->free(ram_cap[i]); } } }; File const *Linker::load(char const *path, bool load) { if (verbose_loading) PDBG("loading: %s (PHDRS only: %s)", path, load ? "no" : "yes"); Elf_file *file = new(env()->heap()) Elf_file(Linker::file(path), load); return file; }