model/elf-cache.cc
author Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
Sun, 03 Oct 2010 21:51:02 +0200
changeset 32 80e4b7d078c4
parent 0 0a55bed4c895
child 164 6522058328b4
permissions -rw-r--r--
handle GNU_RELRO to optimized memory usage

#include "elf-cache.h"
#include "ns3/log.h"
#include "ns3/assert.h"
#include "ns3/fatal-error.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sstream>
#include <string.h>
#include <sys/mman.h>

namespace ns3 {

NS_LOG_COMPONENT_DEFINE("ElfCache");

ElfCache::ElfCache (std::string directory, uint32_t uid)
  : m_directory (directory),
    m_uid (uid)
{
  struct Overriden overriden;
  overriden.from = "libc.so.6";
  overriden.to = "libc-ns3.so";
  m_overriden.push_back (overriden);
  overriden.from = "libpthread.so.0";
  overriden.to = "libpthread-ns3.so";
  m_overriden.push_back (overriden);
}

std::string
ElfCache::GetBasename (std::string filename) const
{
  std::string::size_type tmp = filename.find_last_of ("/");  
  if (tmp == std::string::npos)
    {
      return filename;
    }
  return filename.substr (tmp+1, filename.size () - (tmp+1));
}

void
ElfCache::CopyFile (std::string source, std::string destination) const
{
  NS_LOG_FUNCTION (this << source << destination);
  int src = open (source.c_str (), O_RDONLY);
  int dst = open (destination.c_str (), O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
  uint8_t buffer[1024];
  ssize_t bytes_read = read (src, buffer, 1024);
  while (bytes_read > 0)
    {
      ssize_t bytes_written = 0;
      while (bytes_written != bytes_read)
	{
	  ssize_t written = write (dst, buffer + bytes_written, bytes_read - bytes_written);
	  bytes_written += written;
	}
      bytes_read = read (src, buffer, 1024);
    }
  close (src);
  close (dst);
  NS_LOG_DEBUG ("copied " << source << " to " << destination);
}

long
ElfCache::GetDtStrTab (ElfW(Dyn) *dyn, long baseAddress) const
{
  bool prelinked = false;
  long dt_strtab = 0;
  while (dyn->d_tag != DT_NULL)
    {
      if (dyn->d_tag == DT_STRTAB)
	{
	  dt_strtab = dyn->d_un.d_val;
	}
      else if (dyn->d_tag == DT_GNU_PRELINKED)
	{
	  prelinked = true;
	}
      dyn++;
    }
  if (prelinked)
    {
      dt_strtab -= baseAddress;
    }
  return dt_strtab;
}

unsigned long
ElfCache::GetBaseAddress (ElfW(Phdr) *phdr, long phnum) const
{
  unsigned long end = ~0; // the max value storable in a uint64_t
  unsigned long base = end;
  for (long i = 0; i < phnum; i++, phdr++)
    {
      if (phdr->p_type == PT_LOAD)
	{
	  unsigned long ph_base = phdr->p_vaddr & ~(phdr->p_align-1);
	  if (ph_base < base)
	    {
	      base = ph_base;
	    }
	}
    }
  if (base == end)
    {
      NS_LOG_ERROR ("Could not find base address.");
    }
  return base;
}


uint8_t
ElfCache::NumberToChar (uint8_t c) const
{
  NS_ASSERT (c <= 60);
  if (c < 10)
    {
      return c + 0x30;
    }
  else if (c < (10+25))
    {
      return c - 10 + 0x41;
    }
  else if (c < (10+25+25))
    {
      return c - (10 + 25) + 0x61;
    }
  return 0; // quiet compiler
}
void
ElfCache::WriteString (char *str, uint32_t uid) const
{
  // we use base 4 chars in base 60.
  if (uid >= 60 * 60 * 60 * 60)
    {
      NS_FATAL_ERROR ("Please, report a bug: not enough unique strings for loaded code.");
    }
  uint8_t a = uid % 60;
  uint8_t b = (uid / 60) % 60;
  uint8_t c = (uid / 3600) % 60;
  uint8_t d = (uid / 216000) % 60;
  str[0] = NumberToChar (d);
  str[1] = NumberToChar (c);
  str[2] = NumberToChar (b);
  str[3] = NumberToChar (a);
  NS_LOG_DEBUG ("wrote " << str);
}

struct ElfCache::FileInfo
ElfCache::EditBuffer (uint8_t *map, uint32_t selfId) const
{
  ElfW(Ehdr) *header = (ElfW(Ehdr) *)map;
  ElfW(Phdr) *phdr = (ElfW(Phdr) *)(map + header->e_phoff);
  ElfW(Dyn) *dyn = 0;
  long base_address = GetBaseAddress (phdr, header->e_phnum);

  // find DYNAMIC and fill DataSection
  struct FileInfo fileInfo;
  ElfW(Phdr) *pt_load_rw = 0;
  ElfW(Phdr) *pt_gnu_relro = 0;
  for (uint32_t i = 0; i < header->e_phnum; i++, phdr++)
    {
      switch (phdr->p_type)
	{
	case PT_LOAD:
	  if (phdr->p_flags & PF_W)
	    {
	      // data section !
	      pt_load_rw = phdr;
	    }
	  break;
	case PT_DYNAMIC:
	  // now, seek DT_NEEDED
	  dyn = (ElfW(Dyn) *)(map + phdr->p_offset);
	  break;
	case PT_GNU_RELRO:
	  pt_gnu_relro = phdr;
	  break;
	}
    }
  NS_ASSERT (pt_load_rw != 0);
  fileInfo.p_vaddr = pt_load_rw->p_vaddr;
  fileInfo.p_memsz = pt_load_rw->p_memsz;
  if (pt_gnu_relro != 0)
    {
      NS_ASSERT (pt_gnu_relro->p_vaddr == pt_load_rw->p_vaddr);
      fileInfo.p_vaddr += pt_gnu_relro->p_memsz;
      fileInfo.p_memsz -= pt_gnu_relro->p_memsz;
    }

  // first, Patch the DT_NEEDED, and, DT_SONAME entries
  // and save the DT_INIT entry
  long dt_strtab = GetDtStrTab (dyn, base_address);
  long dt_init = 0;
  ElfW(Dyn) *cur = dyn;
  while (cur->d_tag != DT_NULL)
    {
      if (cur->d_tag == DT_NEEDED)
	{
	  char *needed = (char *)(map + dt_strtab + cur->d_un.d_val);
	  if (std::string (needed) != "ld-linux-x86-64.so.2" &&
	      std::string (needed) != "ld-linux.so.2")
	    {
	      uint32_t id = GetDepId (needed);
	      fileInfo.deps.push_back (id);
	      WriteString (needed, id);
	    }
	}
      else if (cur->d_tag == DT_SONAME)
	{
	  char *soname = (char *)(map + dt_strtab + cur->d_un.d_val);
	  WriteString (soname, selfId);
	}
      else if (cur->d_tag == DT_INIT)
	{
	  dt_init = cur->d_un.d_val;
	}
      cur++;
    }
  // then, eliminate DT_FINI, DT_FINI_ARRAY and DT_FINI_ARRAYSZ
  cur = dyn;
  while (cur->d_tag != DT_NULL)
    {
      if (cur->d_tag == DT_FINI)
	{
	  cur->d_tag = DT_INIT;
	  cur->d_un.d_val = dt_init;
	}
      else if (cur->d_tag == DT_FINI_ARRAYSZ)
	{
	  cur->d_un.d_val = 0;
	}
      cur++;
    }
  return fileInfo;
}

struct ElfCache::FileInfo
ElfCache::EditFile (std::string filename, uint32_t selfId) const
{
  NS_LOG_FUNCTION (this << filename);
  int fd = ::open (filename.c_str (), O_RDWR);
  NS_ASSERT_MSG (fd != -1, "unable to open file=" << filename << " error=" << strerror (errno));
  struct stat st;
  int retval = ::fstat(fd, &st);
  NS_ASSERT_MSG (retval == 0, "unable to fstat file=" << filename << " error=" << strerror (errno));
  uint64_t size = st.st_size;
  uint8_t *buffer = (uint8_t *) ::mmap (0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  NS_ASSERT_MSG (buffer != MAP_FAILED, "unable to mmap file=" << filename << " error=" << strerror (errno));
  close (fd);

  struct FileInfo fileInfo = EditBuffer (buffer, selfId);

  // write back changes to hard disk
  retval = ::msync (buffer, size, MS_SYNC);
  NS_ASSERT_MSG (retval == 0, "msync failed " << strerror (errno));

  retval = ::munmap (buffer, size);
  NS_ASSERT_MSG (retval == 0, "munmap failed " << strerror (errno));

  return fileInfo;
}

uint32_t 
ElfCache::AllocateId (void)
{
  static uint32_t id = 0;
  id++;
  return id;
}

uint32_t
ElfCache::GetDepId (std::string depname) const
{
  for (std::vector<struct Overriden>::const_iterator i = m_overriden.begin (); i != m_overriden.end (); ++i)
    {
      struct Overriden overriden = *i;
      if (overriden.from == depname)
	{
	  depname = overriden.to;
	}
    }
  for (std::vector<struct ElfCachedFile>::const_iterator i = m_files.begin (); i != m_files.end (); ++i)
    {
      if (depname == i->basename)
	{
	  return i->id;
	}
    }
  NS_ASSERT_MSG (false, "did not find " << depname);
  return 0; // quiet compiler
}

std::string
ElfCache::EnsureCacheDirectory (void) const
{
  int retval = ::mkdir (m_directory.c_str (), S_IRWXU);
  if (retval == 0)
    {
      NS_LOG_DEBUG ("Created elf loader cache directory.");
    }
  std::ostringstream oss;
  oss << m_directory << "/" << m_uid;
  retval = ::mkdir (oss.str ().c_str (), S_IRWXU);
  if (retval == 0)
    {
      NS_LOG_DEBUG ("Created elf loader cache directory.");
    }
  return oss.str ();
}

struct ElfCache::ElfCachedFile
ElfCache::Add (std::string filename)
{
  NS_LOG_FUNCTION (this << filename);
  std::string basename = GetBasename (filename);
  // check if we have an override rule for this file
  for (std::vector<struct Overriden>::const_iterator i = m_overriden.begin (); i != m_overriden.end (); ++i)
    {
      struct Overriden overriden = *i;
      if (overriden.from == basename)
	{
	  // check if the overriden file is already in-store.
	  for (std::vector<struct ElfCachedFile>::const_iterator i = m_files.begin (); i != m_files.end (); ++i)
	    {
	      if (i->basename == overriden.to)
		{
		  return *i;
		}
	    }
	  NS_ASSERT (false);
	}
    }

  // check if the file is already in-store.
  for (std::vector<struct ElfCachedFile>::const_iterator i = m_files.begin (); i != m_files.end (); ++i)
    {
      if (i->basename == basename)
	{
	  return *i;
	}
    }  

  std::string directory = EnsureCacheDirectory ();
  std::string fileCopy = directory + "/" + basename;
  CopyFile (filename, fileCopy);

  uint32_t selfId = AllocateId ();

  struct FileInfo fileInfo = EditFile (fileCopy, selfId);

  struct ElfCachedFile cached;
  cached.cachedFilename = fileCopy;
  cached.basename = basename;
  cached.data_p_vaddr = fileInfo.p_vaddr;
  cached.data_p_memsz = fileInfo.p_memsz;
  cached.id = selfId;
  cached.deps = fileInfo.deps;

  m_files.push_back (cached);
  return cached;
}


} // namespace ns3