base: support dynamic loading of Linux ELF objects in SE mode
authorBrandon Potter <brandon.potter@amd.com>
Thu, 17 Mar 2016 17:31:03 +0000 (10:31 -0700)
committerBrandon Potter <brandon.potter@amd.com>
Thu, 17 Mar 2016 17:31:03 +0000 (10:31 -0700)
src/arch/alpha/process.cc
src/arch/arm/process.cc
src/arch/mips/process.cc
src/arch/power/process.cc
src/arch/sparc/process.cc
src/arch/x86/process.cc
src/base/loader/elf_object.cc
src/base/loader/elf_object.hh
src/base/loader/object_file.hh
src/sim/process.cc
src/sim/process.hh

index 54ef338f304ea4c8d00e12466ddbff8dcc789c30..9e298f0c6807ddade781b4f571bfef432a263a0e 100644 (file)
@@ -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<uint64_t> 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
index 0c6f48fb54df878c34874a31fb8282f62f6a8107..a787b1f669a6fbcf4e5fac0c3bbe7046a66de28f 100644 (file)
@@ -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.
index 8754191fa301fbdf697302e51a5723f6d2f75c8f..6947eeafd0bab35caf653067bbac7af4db69677f 100644 (file)
@@ -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());
 }
 
 
index a770d8137b446e99ea3edbab64e613b232361902..10e4a85a06c8d8e0fa55f5bcdec70db63513fa35 100644 (file)
@@ -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);
index 8c8be65ab35ce26d917e2118278c58a7f6509cb0..09d52ae6b1c27d7108ed2420648eec4c2e639a36 100644 (file)
@@ -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);
index d2ce4dbd1eac59ee3a013b1b7ee9f010bc177209..66a520bc3f4cfe4e229761e7ec4f082e5a4523b2 100644 (file)
@@ -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<ElfObject *>(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);
index 6bfc0e4603dcbb8c9fc8fd59740c2f1b231e10c7..e0b0439c80be4ec511cca3c0a07c07338289f9e5 100644 (file)
  *          Ali Saidi
  */
 
+#include "base/loader/elf_object.hh"
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
 #include <cassert>
 #include <string>
 
-#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<ElfObject*>(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<Addr>::max()),
+      ldMax(std::numeric_limits<Addr>::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;
+}
index 96989629079d2e759b5be87b9a2c9bee662c9e54..cec20a47a784062b420a9d6eeb312ecc31e7f673 100644 (file)
@@ -62,6 +62,24 @@ class ElfObject : public ObjectFile
     uint16_t _programHeaderCount;
     std::set<std::string> 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<Addr>::max(),
-            Addr offset = 0);
+            Addr offset = 0) override;
     virtual bool loadGlobalSymbols(SymbolTable *symtab, Addr addrMask =
-            std::numeric_limits<Addr>::max());
+            std::numeric_limits<Addr>::max()) override;
     virtual bool loadLocalSymbols(SymbolTable *symtab, Addr addrMask =
-            std::numeric_limits<Addr>::max());
+            std::numeric_limits<Addr>::max()) override;
     virtual bool loadWeakSymbols(SymbolTable *symtab, Addr addrMask =
-            std::numeric_limits<Addr>::max());
+            std::numeric_limits<Addr>::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;}
index e5961add255edfcfa0e9c05b905ef8ef30e815f4..26750038fbe2fb72e5673dc0de0ae520fd81fa4a 100644 (file)
@@ -35,6 +35,7 @@
 #include <limits>
 #include <string>
 
+#include "base/misc.hh"
 #include "base/types.hh"
 
 class PortProxy;
@@ -94,7 +95,14 @@ class ObjectFile
             std::numeric_limits<Addr>::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; }
index 81a7ec89e3b71f627a65c2648d1c33f3be01bac7..7fa160995c726ba19fae050252fa4ca24c9c305f 100644 (file)
@@ -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).");
index 72f789ec735abd78b798fa62df78bc5a97dccfda..aa4c7a0085c0bf2fea089515ee67ab0586de8ab3 100644 (file)
@@ -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.