From: Brandon Potter Date: Thu, 17 Mar 2016 17:31:03 +0000 (-0700) Subject: base: support dynamic loading of Linux ELF objects in SE mode X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=9b4249410ec18cac9df2c7e9c0a4a6ce5459233d;p=gem5.git base: support dynamic loading of Linux ELF objects in SE mode --- diff --git a/src/arch/alpha/process.cc b/src/arch/alpha/process.cc index 54ef338f3..9e298f0c6 100644 --- a/src/arch/alpha/process.cc +++ b/src/arch/alpha/process.cc @@ -67,6 +67,9 @@ AlphaLiveProcess::AlphaLiveProcess(LiveProcessParams *params, void AlphaLiveProcess::argsInit(int intSize, int pageSize) { + // Patch the ld_bias for dynamic executables. + updateBias(); + objFile->loadSections(initVirtMem); typedef AuxVector auxv_t; @@ -88,6 +91,10 @@ AlphaLiveProcess::argsInit(int intSize, int pageSize) auxv.push_back(auxv_t(M5_AT_PHDR, elfObject->programHeaderTable())); DPRINTF(Loader, "auxv at PHDR %08p\n", elfObject->programHeaderTable()); auxv.push_back(auxv_t(M5_AT_PHNUM, elfObject->programHeaderCount())); + // This is the base address of the ELF interpreter; it should be + // zero for static executables or contain the base address for + // dynamic executables. + auxv.push_back(auxv_t(M5_AT_BASE, getBias())); auxv.push_back(auxv_t(M5_AT_ENTRY, objFile->entryPoint())); auxv.push_back(auxv_t(M5_AT_UID, uid())); auxv.push_back(auxv_t(M5_AT_EUID, euid())); @@ -163,7 +170,7 @@ AlphaLiveProcess::argsInit(int intSize, int pageSize) setSyscallArg(tc, 1, argv_array_base); tc->setIntReg(StackPointerReg, stack_min); - tc->pcState(objFile->entryPoint()); + tc->pcState(getStartPC()); } void diff --git a/src/arch/arm/process.cc b/src/arch/arm/process.cc index 0c6f48fb5..a787b1f66 100644 --- a/src/arch/arm/process.cc +++ b/src/arch/arm/process.cc @@ -156,6 +156,9 @@ ArmLiveProcess::argsInit(int pageSize, IntRegIndex spIndex) //We want 16 byte alignment uint64_t align = 16; + // Patch the ld_bias for dynamic executables. + updateBias(); + // load object file into target memory objFile->loadSections(initVirtMem); @@ -225,10 +228,10 @@ ArmLiveProcess::argsInit(int pageSize, IntRegIndex spIndex) auxv.push_back(auxv_t(M5_AT_PHENT, elfObject->programHeaderSize())); // This is the number of program headers from the original elf file. auxv.push_back(auxv_t(M5_AT_PHNUM, elfObject->programHeaderCount())); - //This is the address of the elf "interpreter", It should be set - //to 0 for regular executables. It should be something else - //(not sure what) for dynamic libraries. - auxv.push_back(auxv_t(M5_AT_BASE, 0)); + // This is the base address of the ELF interpreter; it should be + // zero for static executables or contain the base address for + // dynamic executables. + auxv.push_back(auxv_t(M5_AT_BASE, getBias())); //XXX Figure out what this should be. auxv.push_back(auxv_t(M5_AT_FLAGS, 0)); //The entry point to the program @@ -392,7 +395,7 @@ ArmLiveProcess::argsInit(int pageSize, IntRegIndex spIndex) pc.nextThumb(pc.thumb()); pc.aarch64(arch == ObjectFile::Arm64); pc.nextAArch64(pc.aarch64()); - pc.set(objFile->entryPoint() & ~mask(1)); + pc.set(getStartPC() & ~mask(1)); tc->pcState(pc); //Align the "stack_min" to a page boundary. diff --git a/src/arch/mips/process.cc b/src/arch/mips/process.cc index 8754191fa..6947eeafd 100644 --- a/src/arch/mips/process.cc +++ b/src/arch/mips/process.cc @@ -78,6 +78,9 @@ MipsLiveProcess::argsInit(int pageSize) { int intSize = sizeof(IntType); + // Patch the ld_bias for dynamic executables. + updateBias(); + // load object file into target memory objFile->loadSections(initVirtMem); @@ -100,6 +103,10 @@ MipsLiveProcess::argsInit(int pageSize) auxv.push_back(auxv_t(M5_AT_PHENT, elfObject->programHeaderSize())); // This is the number of program headers from the original elf file. auxv.push_back(auxv_t(M5_AT_PHNUM, elfObject->programHeaderCount())); + // This is the base address of the ELF interpreter; it should be + // zero for static executables or contain the base address for + // dynamic executables. + auxv.push_back(auxv_t(M5_AT_BASE, getBias())); //The entry point to the program auxv.push_back(auxv_t(M5_AT_ENTRY, objFile->entryPoint())); //Different user and group IDs @@ -177,7 +184,7 @@ MipsLiveProcess::argsInit(int pageSize) setSyscallArg(tc, 1, argv_array_base); tc->setIntReg(StackPointerReg, stack_min); - tc->pcState(objFile->entryPoint()); + tc->pcState(getStartPC()); } diff --git a/src/arch/power/process.cc b/src/arch/power/process.cc index a770d8137..10e4a85a0 100644 --- a/src/arch/power/process.cc +++ b/src/arch/power/process.cc @@ -85,6 +85,9 @@ PowerLiveProcess::argsInit(int intSize, int pageSize) //We want 16 byte alignment uint64_t align = 16; + // Patch the ld_bias for dynamic executables. + updateBias(); + // load object file into target memory objFile->loadSections(initVirtMem); @@ -108,11 +111,10 @@ PowerLiveProcess::argsInit(int intSize, int pageSize) auxv.push_back(auxv_t(M5_AT_PHENT, elfObject->programHeaderSize())); // This is the number of program headers from the original elf file. auxv.push_back(auxv_t(M5_AT_PHNUM, elfObject->programHeaderCount())); - //This is the address of the elf "interpreter", It should be set - //to 0 for regular executables. It should be something else - //(not sure what) for dynamic libraries. - auxv.push_back(auxv_t(M5_AT_BASE, 0)); - + // This is the base address of the ELF interpreter; it should be + // zero for static executables or contain the base address for + // dynamic executables. + auxv.push_back(auxv_t(M5_AT_BASE, getBias())); //XXX Figure out what this should be. auxv.push_back(auxv_t(M5_AT_FLAGS, 0)); //The entry point to the program @@ -255,7 +257,7 @@ PowerLiveProcess::argsInit(int intSize, int pageSize) //Set the stack pointer register tc->setIntReg(StackPointerReg, stack_min); - tc->pcState(objFile->entryPoint()); + tc->pcState(getStartPC()); //Align the "stack_min" to a page boundary. stack_min = roundDown(stack_min, pageSize); diff --git a/src/arch/sparc/process.cc b/src/arch/sparc/process.cc index 8c8be65ab..09d52ae6b 100644 --- a/src/arch/sparc/process.cc +++ b/src/arch/sparc/process.cc @@ -203,6 +203,9 @@ SparcLiveProcess::argsInit(int pageSize) // maintain double word alignment of the stack pointer. uint64_t align = 16; + // Patch the ld_bias for dynamic executables. + updateBias(); + // load object file into target memory objFile->loadSections(initVirtMem); @@ -245,10 +248,10 @@ SparcLiveProcess::argsInit(int pageSize) auxv.push_back(auxv_t(M5_AT_PHENT, elfObject->programHeaderSize())); // This is the number of program headers from the original elf file. auxv.push_back(auxv_t(M5_AT_PHNUM, elfObject->programHeaderCount())); - // This is the address of the elf "interpreter", It should be set - // to 0 for regular executables. It should be something else - // (not sure what) for dynamic libraries. - auxv.push_back(auxv_t(M5_AT_BASE, 0)); + // This is the base address of the ELF interpreter; it should be + // zero for static executables or contain the base address for + // dynamic executables. + auxv.push_back(auxv_t(M5_AT_BASE, getBias())); // This is hardwired to 0 in the elf loading code in the kernel auxv.push_back(auxv_t(M5_AT_FLAGS, 0)); // The entry point to the program @@ -402,7 +405,7 @@ SparcLiveProcess::argsInit(int pageSize) // don't have anything like that, it should be set to 0. tc->setIntReg(1, 0); - tc->pcState(objFile->entryPoint()); + tc->pcState(getStartPC()); // Align the "stack_min" to a page boundary. stack_min = roundDown(stack_min, pageSize); diff --git a/src/arch/x86/process.cc b/src/arch/x86/process.cc index d2ce4dbd1..66a520bc3 100644 --- a/src/arch/x86/process.cc +++ b/src/arch/x86/process.cc @@ -756,6 +756,9 @@ X86LiveProcess::argsInit(int pageSize, //We want 16 byte alignment uint64_t align = 16; + // Patch the ld_bias for dynamic executables. + updateBias(); + // load object file into target memory objFile->loadSections(initVirtMem); @@ -798,8 +801,10 @@ X86LiveProcess::argsInit(int pageSize, X86_IA64Processor = 1 << 30 }; - // Setup the auxilliary vectors. These will already have endian conversion. - // Auxilliary vectors are loaded only for elf formatted executables. + // Setup the auxiliary vectors. These will already have endian + // conversion. Auxiliary vectors are loaded only for elf formatted + // executables; the auxv is responsible for passing information from + // the OS to the interpreter. ElfObject * elfObject = dynamic_cast(objFile); if (elfObject) { uint64_t features = @@ -842,18 +847,17 @@ X86LiveProcess::argsInit(int pageSize, //Frequency at which times() increments //Defined to be 100 in the kernel source. auxv.push_back(auxv_t(M5_AT_CLKTCK, 100)); - // For statically linked executables, this is the virtual address of the - // program header tables if they appear in the executable image + // This is the virtual address of the program header tables if they + // appear in the executable image. auxv.push_back(auxv_t(M5_AT_PHDR, elfObject->programHeaderTable())); // This is the size of a program header entry from the elf file. auxv.push_back(auxv_t(M5_AT_PHENT, elfObject->programHeaderSize())); // This is the number of program headers from the original elf file. auxv.push_back(auxv_t(M5_AT_PHNUM, elfObject->programHeaderCount())); - //This is the address of the elf "interpreter", It should be set - //to 0 for regular executables. It should be something else - //(not sure what) for dynamic libraries. - auxv.push_back(auxv_t(M5_AT_BASE, 0)); - + // This is the base address of the ELF interpreter; it should be + // zero for static executables or contain the base address for + // dynamic executables. + auxv.push_back(auxv_t(M5_AT_BASE, getBias())); //XXX Figure out what this should be. auxv.push_back(auxv_t(M5_AT_FLAGS, 0)); //The entry point to the program @@ -1014,7 +1018,7 @@ X86LiveProcess::argsInit(int pageSize, // There doesn't need to be any segment base added in since we're dealing // with the flat segmentation model. - tc->pcState(objFile->entryPoint()); + tc->pcState(getStartPC()); //Align the "stack_min" to a page boundary. stack_min = roundDown(stack_min, pageSize); diff --git a/src/base/loader/elf_object.cc b/src/base/loader/elf_object.cc index 6bfc0e460..e0b0439c8 100644 --- a/src/base/loader/elf_object.cc +++ b/src/base/loader/elf_object.cc @@ -41,22 +41,30 @@ * Ali Saidi */ +#include "base/loader/elf_object.hh" + +#include +#include +#include +#include +#include + #include #include -#include "base/loader/elf_object.hh" -#include "base/loader/symtab.hh" #include "base/bitfield.hh" +#include "base/loader/symtab.hh" #include "base/misc.hh" #include "base/trace.hh" #include "debug/Loader.hh" -#include "sim/byteswap.hh" #include "gelf.h" +#include "sim/byteswap.hh" using namespace std; ObjectFile * -ElfObject::tryFile(const string &fname, size_t len, uint8_t *data) +ElfObject::tryFile(const string &fname, size_t len, uint8_t *data, + bool skip_interp_check) { Elf *elf; GElf_Ehdr ehdr; @@ -243,6 +251,41 @@ ElfObject::tryFile(const string &fname, size_t len, uint8_t *data) result->_programHeaderTable = 0; + if (!skip_interp_check) { + for (int i = 0; i < ehdr.e_phnum; i++) { + GElf_Phdr phdr; + M5_VAR_USED void *check_p = gelf_getphdr(elf, i, &phdr); + assert(check_p != nullptr); + + if (phdr.p_type != PT_INTERP) + continue; + + char *interp_path = (char*)data + phdr.p_offset; + int fd = open(interp_path, O_RDONLY); + if (fd == -1) { + fatal("Unable to open dynamic executable's " + "interpreter.\n"); + } + + struct stat sb; + M5_VAR_USED int check_i = fstat(fd, &sb); + assert(check_i == 0); + + void *mm = mmap(nullptr, sb.st_size, PROT_READ, + MAP_PRIVATE, fd, 0); + assert(mm != MAP_FAILED); + ::close(fd); + + uint8_t *interp_image = (uint8_t*)mm; + ObjectFile *obj = tryFile(interp_path, sb.st_size, + interp_image, true); + assert(obj != nullptr); + result->interpreter = dynamic_cast(obj); + assert(result->interpreter != nullptr); + break; + } + } + elf_end(elf); return result; } @@ -252,8 +295,10 @@ ElfObject::tryFile(const string &fname, size_t len, uint8_t *data) ElfObject::ElfObject(const string &_filename, size_t _len, uint8_t *_data, Arch _arch, OpSys _opSys) : ObjectFile(_filename, _len, _data, _arch, _opSys), - _programHeaderTable(0), _programHeaderSize(0), _programHeaderCount(0) - + _programHeaderTable(0), _programHeaderSize(0), _programHeaderCount(0), + interpreter(nullptr), ldBias(0), relocate(true), + ldMin(std::numeric_limits::max()), + ldMax(std::numeric_limits::min()) { Elf *elf; GElf_Ehdr ehdr; @@ -326,6 +371,9 @@ ElfObject::ElfObject(const string &_filename, size_t _len, uint8_t *_data, if (!(phdr.p_type & PT_LOAD)) continue; + ldMin = std::min(ldMin, phdr.p_vaddr); + ldMax = std::max(ldMax, phdr.p_vaddr + phdr.p_memsz); + // Check to see if this segment contains the bss section. if (phdr.p_paddr <= bssSecStart && phdr.p_paddr + phdr.p_memsz > bssSecStart && @@ -338,6 +386,11 @@ ElfObject::ElfObject(const string &_filename, size_t _len, uint8_t *_data, // Check to see if this is the text or data segment if (phdr.p_vaddr <= textSecStart && phdr.p_vaddr + phdr.p_filesz > textSecStart) { + + // If this value is nonzero, we need to flip the relocate flag. + if (phdr.p_vaddr != 0) + relocate = false; + text.baseAddr = phdr.p_paddr; text.size = phdr.p_filesz; text.fileImage = fileData + phdr.p_offset; @@ -462,6 +515,10 @@ ElfObject::loadSections(PortProxy& memProxy, Addr addrMask, Addr offset) return false; } } + + if (interpreter) + interpreter->loadSections(memProxy, addrMask, offset); + return true; } @@ -510,3 +567,19 @@ ElfObject::sectionExists(string sec) } +void +ElfObject::updateBias(Addr bias_addr) +{ + // Record the bias. + ldBias = bias_addr; + + // Patch the entry point with bias_addr. + entry += bias_addr; + + // Patch segments with the bias_addr. + text.baseAddr += bias_addr; + data.baseAddr += bias_addr; + bss.baseAddr += bias_addr; + for (auto &segment : extraSegments) + segment.baseAddr += bias_addr; +} diff --git a/src/base/loader/elf_object.hh b/src/base/loader/elf_object.hh index 969896290..cec20a47a 100644 --- a/src/base/loader/elf_object.hh +++ b/src/base/loader/elf_object.hh @@ -62,6 +62,24 @@ class ElfObject : public ObjectFile uint16_t _programHeaderCount; std::set sectionNames; + ElfObject *interpreter; + + // An interpreter load bias is the location in the process address space + // where the interpreter is chosen to reside. Typically, this is carved + // out of the top of the mmap reserve section. + Addr ldBias; + + // The interpreter is typically a relocatable shared library and will + // have a default value of zero which means that it does not care where + // it is placed. However, the loader can be compiled and linked so that + // it does care and needs a specific entry point. + bool relocate; + + // The ldMin and ldMax fields are required to know how large of an + // area is required to map the interpreter. + Addr ldMin; + Addr ldMax; + /// Helper functions for loadGlobalSymbols() and loadLocalSymbols(). bool loadSomeSymbols(SymbolTable *symtab, int binding, Addr mask); @@ -78,19 +96,26 @@ class ElfObject : public ObjectFile bool loadSections(PortProxy& memProxy, Addr addrMask = std::numeric_limits::max(), - Addr offset = 0); + Addr offset = 0) override; virtual bool loadGlobalSymbols(SymbolTable *symtab, Addr addrMask = - std::numeric_limits::max()); + std::numeric_limits::max()) override; virtual bool loadLocalSymbols(SymbolTable *symtab, Addr addrMask = - std::numeric_limits::max()); + std::numeric_limits::max()) override; virtual bool loadWeakSymbols(SymbolTable *symtab, Addr addrMask = - std::numeric_limits::max()); + std::numeric_limits::max()) override; + + virtual ObjectFile *getInterpreter() const override + { return interpreter; } + virtual Addr bias() const override { return ldBias; } + virtual bool relocatable() const override { return relocate; } + virtual Addr mapSize() const override { return ldMax - ldMin; } + virtual void updateBias(Addr bias_addr) override; - virtual bool isDynamic() { return sectionExists(".interp"); } - virtual bool hasTLS() { return sectionExists(".tbss"); } + virtual bool hasTLS() override { return sectionExists(".tbss"); } static ObjectFile *tryFile(const std::string &fname, - size_t len, uint8_t *data); + size_t len, uint8_t *data, + bool skip_interp_check = false); Addr programHeaderTable() {return _programHeaderTable;} uint16_t programHeaderSize() {return _programHeaderSize;} uint16_t programHeaderCount() {return _programHeaderCount;} diff --git a/src/base/loader/object_file.hh b/src/base/loader/object_file.hh index e5961add2..26750038f 100644 --- a/src/base/loader/object_file.hh +++ b/src/base/loader/object_file.hh @@ -35,6 +35,7 @@ #include #include +#include "base/misc.hh" #include "base/types.hh" class PortProxy; @@ -94,7 +95,14 @@ class ObjectFile std::numeric_limits::max()) { return false; } - virtual bool isDynamic() { return false; } + virtual ObjectFile *getInterpreter() const { return nullptr; } + virtual bool relocatable() const { return false; } + virtual Addr mapSize() const + { panic("mapSize() should only be called on relocatable objects\n"); } + virtual void updateBias(Addr bias_addr) + { panic("updateBias() should only be called on relocatable objects\n"); } + virtual Addr bias() const { return 0; } + virtual bool hasTLS() { return false; } Arch getArch() const { return arch; } diff --git a/src/sim/process.cc b/src/sim/process.cc index 81a7ec89e..7fa160995 100644 --- a/src/sim/process.cc +++ b/src/sim/process.cc @@ -518,6 +518,48 @@ LiveProcess::findDriver(std::string filename) return NULL; } +void +LiveProcess::updateBias() +{ + ObjectFile *interp = objFile->getInterpreter(); + + if (!interp || !interp->relocatable()) + return; + + // Determine how large the interpreters footprint will be in the process + // address space. + Addr interp_mapsize = roundUp(interp->mapSize(), TheISA::PageBytes); + + // We are allocating the memory area; set the bias to the lowest address + // in the allocated memory region. + Addr ld_bias = mmapGrowsDown() ? mmap_end - interp_mapsize : mmap_end; + + // Adjust the process mmap area to give the interpreter room; the real + // execve system call would just invoke the kernel's internal mmap + // functions to make these adjustments. + mmap_end = mmapGrowsDown() ? ld_bias : mmap_end + interp_mapsize; + + interp->updateBias(ld_bias); +} + + +Addr +LiveProcess::getBias() +{ + ObjectFile *interp = objFile->getInterpreter(); + + return interp ? interp->bias() : objFile->bias(); +} + + +Addr +LiveProcess::getStartPC() +{ + ObjectFile *interp = objFile->getInterpreter(); + + return interp ? interp->entryPoint() : objFile->entryPoint(); +} + LiveProcess * LiveProcess::create(LiveProcessParams * params) @@ -535,11 +577,6 @@ LiveProcess::create(LiveProcessParams * params) fatal("Can't load object file %s", params->executable); } - if (objFile->isDynamic()) - fatal("Object file is a dynamic executable however only static " - "executables are supported!\n Please recompile your " - "executable as a static binary and try again.\n"); - #if THE_ISA == ALPHA_ISA if (objFile->getArch() != ObjectFile::Alpha) fatal("Object file architecture does not match compiled ISA (Alpha)."); diff --git a/src/sim/process.hh b/src/sim/process.hh index 72f789ec7..aa4c7a008 100644 --- a/src/sim/process.hh +++ b/src/sim/process.hh @@ -336,6 +336,14 @@ class LiveProcess : public Process */ EmulatedDriver *findDriver(std::string filename); + // This function acts as a callback to update the bias value in + // the object file because the parameters needed to calculate the + // bias are not available when the object file is created. + void updateBias(); + + Addr getBias(); + Addr getStartPC(); + // this function is used to create the LiveProcess object, since // we can't tell which subclass of LiveProcess to use until we // open and look at the object file.