genode/repos/base/src/lib/ldso/file.cc

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;
}