base: support dynamic loading of Linux ELF objects in SE mode

This commit is contained in:
Brandon Potter 2016-03-17 10:31:03 -07:00
parent 4fc69db8f8
commit 9b4249410e
11 changed files with 224 additions and 47 deletions

View 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

View 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.

View 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());
}

View 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);

View 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);

View 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);

View file

@ -41,22 +41,30 @@
* 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;
}

View 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 bool isDynamic() { return sectionExists(".interp"); }
virtual bool hasTLS() { return sectionExists(".tbss"); }
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 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;}

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

View 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).");

View 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.