308 lines
7.0 KiB
C++
308 lines
7.0 KiB
C++
/**
|
|
* \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 <linker.h>
|
|
|
|
#include <base/allocator_avl.h>
|
|
#include <os/attached_rom_dataspace.h>
|
|
#include <rm_session/connection.h>
|
|
#include <util/construct_at.h>
|
|
|
|
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<Rm_area>(placeholder, base);
|
|
constructed = 1;
|
|
}
|
|
return reinterpret_cast<Rm_area *>(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;
|
|
}
|
|
|