Commit 942b6613 authored by Mike Hommey's avatar Mike Hommey
Browse files

Bug 686805 part 4 - Make the linker load libraries with on-demand...

Bug 686805 part 4 - Make the linker load libraries with on-demand decompression when they are seekable compressed streams. r=tglek,r=sewardj
parent ee925601
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -326,6 +326,7 @@ private:
      void (*func)(void);
    } f;
    f.ptr = ptr;
    debug("%s: Calling function @%p", GetPath(), ptr);
    f.func();
  }

+12 −10
Original line number Diff line number Diff line
@@ -226,7 +226,6 @@ ElfLoader::Load(const char *path, int flags, LibHandle *parent)
    zip = zips.GetZip(zip_path);
    Zip::Stream s;
    if (zip && zip->GetStream(subpath, &s)) {
      if (s.GetType() == Zip::Stream::DEFLATE) {
      /* When the MOZ_LINKER_EXTRACT environment variable is set to "1",
       * compressed libraries are going to be (temporarily) extracted as
       * files, in the directory pointed by the MOZ_LINKER_CACHE
@@ -234,9 +233,12 @@ ElfLoader::Load(const char *path, int flags, LibHandle *parent)
      const char *extract = getenv("MOZ_LINKER_EXTRACT");
      if (extract && !strncmp(extract, "1", 2 /* Including '\0' */))
        mappable = MappableExtractFile::Create(name, &s);
        /* The above may fail in some cases. */
        if (!mappable)
      if (!mappable) {
        if (s.GetType() == Zip::Stream::DEFLATE) {
          mappable = MappableDeflate::Create(name, zip, &s);
        } else if (s.GetType() == Zip::Stream::STORE) {
          mappable = MappableSeekableZStream::Create(name, zip, &s);
        }
      }
    }
  }
+1 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ CPPSRCS += \
  ElfLoader.cpp \
  CustomElf.cpp \
  Mappable.cpp \
  SeekableZStream.cpp \
  $(NULL)
endif

+230 −28
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
#include <linux/ashmem.h>
#endif
#include "ElfLoader.h"
#include "SeekableZStream.h"
#include "Logging.h"

#ifndef PAGE_SIZE
@@ -80,6 +81,7 @@ MappableExtractFile::Create(const char *name, Zip::Stream *stream)
    return NULL;
  }
  AutoUnlinkFile file = path.forget();
  if (stream->GetType() == Zip::Stream::DEFLATE) {
    if (ftruncate(fd, stream->GetUncompressedSize()) == -1) {
      log("Couldn't ftruncate %s to decompress library", file.get());
      return NULL;
@@ -91,6 +93,7 @@ MappableExtractFile::Create(const char *name, Zip::Stream *stream)
      log("Couldn't map %s to decompress library", file.get());
      return NULL;
    }

    z_stream zStream = stream->GetZStream(buffer);

    /* Decompress */
@@ -111,6 +114,30 @@ MappableExtractFile::Create(const char *name, Zip::Stream *stream)
          static_cast<unsigned int>(stream->GetUncompressedSize()));
      return NULL;
    }
  } else if (stream->GetType() == Zip::Stream::STORE) {
    SeekableZStream zStream;
    if (!zStream.Init(stream->GetBuffer())) {
      log("Couldn't initialize SeekableZStream for %s", name);
      return NULL;
    }
    if (ftruncate(fd, zStream.GetUncompressedSize()) == -1) {
      log("Couldn't ftruncate %s to decompress library", file.get());
      return NULL;
    }
    MappedPtr buffer(::mmap(NULL, zStream.GetUncompressedSize(), PROT_WRITE,
                            MAP_SHARED, fd, 0), zStream.GetUncompressedSize());
    if (buffer == MAP_FAILED) {
      log("Couldn't map %s to decompress library", file.get());
      return NULL;
    }

    if (!zStream.Decompress(buffer, 0, zStream.GetUncompressedSize())) {
      log("%s: failed to decompress", name);
      return NULL;
    }
  } else {
    return NULL;
  }

  return new MappableExtractFile(fd.forget(), file.forget());
}
@@ -296,3 +323,178 @@ MappableDeflate::finalize()
  /* Remove reference to Zip archive */
  zip = NULL;
}

MappableSeekableZStream *
MappableSeekableZStream::Create(const char *name, Zip *zip,
                                Zip::Stream *stream)
{
  MOZ_ASSERT(stream->GetType() == Zip::Stream::STORE);
  AutoDeletePtr<MappableSeekableZStream> mappable =
    new MappableSeekableZStream(zip);

  if (pthread_mutex_init(&mappable->mutex, NULL))
    return NULL;

  if (!mappable->zStream.Init(stream->GetBuffer()))
    return NULL;

  mappable->buffer = _MappableBuffer::Create(name,
                              mappable->zStream.GetUncompressedSize());
  if (!mappable->buffer)
    return NULL;

  mappable->chunkAvail = new unsigned char[mappable->zStream.GetChunksNum()];
  memset(mappable->chunkAvail, 0, mappable->zStream.GetChunksNum());

  return mappable.forget();
}

MappableSeekableZStream::MappableSeekableZStream(Zip *zip)
: zip(zip) { }

MappableSeekableZStream::~MappableSeekableZStream()
{
  pthread_mutex_destroy(&mutex);
}

void *
MappableSeekableZStream::mmap(const void *addr, size_t length, int prot,
                              int flags, off_t offset)
{
  /* Map with PROT_NONE so that accessing the mapping would segfault, and
   * bring us to ensure() */
  void *res = buffer->mmap(addr, length, PROT_NONE, flags, offset);
  if (res == MAP_FAILED)
    return MAP_FAILED;

  /* Store the mapping, ordered by offset and length */
  std::vector<LazyMap>::reverse_iterator it;
  for (it = lazyMaps.rbegin(); it < lazyMaps.rend(); ++it) {
    if ((it->offset < offset) ||
        ((it->offset == offset) && (it->length < length)))
      break;
  }
  LazyMap map = { res, length, prot, offset };
  lazyMaps.insert(it.base(), map);
  return res;
}

void
MappableSeekableZStream::munmap(void *addr, size_t length)
{
  std::vector<LazyMap>::iterator it;
  for (it = lazyMaps.begin(); it < lazyMaps.end(); ++it)
    if ((it->addr = addr) && (it->length == length)) {
      lazyMaps.erase(it);
      ::munmap(addr, length);
      return;
    }
  MOZ_NOT_REACHED("munmap called with unknown mapping");
}

void
MappableSeekableZStream::finalize() { }

class AutoLock {
public:
  AutoLock(pthread_mutex_t *mutex): mutex(mutex)
  {
    if (pthread_mutex_lock(mutex))
      MOZ_NOT_REACHED("pthread_mutex_lock failed");
  }
  ~AutoLock()
  {
    if (pthread_mutex_unlock(mutex))
      MOZ_NOT_REACHED("pthread_mutex_unlock failed");
  }
private:
  pthread_mutex_t *mutex;
};

bool
MappableSeekableZStream::ensure(const void *addr)
{
  debug("ensure @%p", addr);
  void *addrPage = reinterpret_cast<void *>
                   (reinterpret_cast<uintptr_t>(addr) & PAGE_MASK);
  /* Find the mapping corresponding to the given page */
  std::vector<LazyMap>::iterator map;
  for (map = lazyMaps.begin(); map < lazyMaps.end(); ++map) {
    if (map->Contains(addrPage))
      break;
  }
  if (map == lazyMaps.end())
    return false;

  /* Find corresponding chunk */
  off_t mapOffset = map->offsetOf(addrPage);
  size_t chunk = mapOffset / zStream.GetChunkSize();

  /* In the typical case, we just need to decompress the chunk entirely. But
   * when the current mapping ends in the middle of the chunk, we want to
   * stop there. However, if another mapping needs the last part of the
   * chunk, we still need to continue. As mappings are ordered by offset
   * and length, we don't need to scan the entire list of mappings.
   * It is safe to run through lazyMaps here because the linker is never
   * going to call mmap (which adds lazyMaps) while this function is
   * called. */
  size_t length = zStream.GetChunkSize(chunk);
  size_t chunkStart = chunk * zStream.GetChunkSize();
  size_t chunkEnd = chunkStart + length;
  std::vector<LazyMap>::iterator it;
  for (it = map; it < lazyMaps.end(); ++it) {
    if (chunkEnd <= it->endOffset())
      break;
  }
  if ((it == lazyMaps.end()) || (chunkEnd > it->endOffset())) {
    /* The mapping "it" points at now is past the interesting one */
    --it;
    length = it->endOffset() - chunkStart;
  }

  AutoLock lock(&mutex);

  /* The very first page is mapped and accessed separately of the rest, and
   * as such, only the first page of the first chunk is decompressed this way.
   * When we fault in the remaining pages of that chunk, we want to decompress
   * the complete chunk again. Short of doing that, we would end up with
   * no data between PAGE_SIZE and chunkSize, which would effectively corrupt
   * symbol resolution in the underlying library. */
  if (chunkAvail[chunk] < (length + PAGE_SIZE - 1) / PAGE_SIZE) {
    if (!zStream.DecompressChunk(*buffer + chunkStart, chunk, length))
      return false;

#if defined(ANDROID) && defined(__arm__)
    if (map->prot & PROT_EXEC) {
      /* We just extracted data that may be executed in the future.
       * We thus need to ensure Instruction and Data cache coherency. */
      debug("cacheflush(%p, %p)", *buffer + chunkStart, *buffer + (chunkStart + length));
      cacheflush(reinterpret_cast<uintptr_t>(*buffer + chunkStart),
                 reinterpret_cast<uintptr_t>(*buffer + (chunkStart + length)), 0);
    }
#endif
    chunkAvail[chunk] = (length + PAGE_SIZE - 1) / PAGE_SIZE;
  }

  /* Flip the chunk mapping protection to the recorded flags. We could
   * also flip the protection for other mappings of the same chunk,
   * but it's easier to skip that and let further segfaults call
   * ensure again. */
  const void *chunkAddr = reinterpret_cast<const void *>
                          (reinterpret_cast<uintptr_t>(addrPage)
                           - mapOffset % zStream.GetChunkSize());
  const void *chunkEndAddr = reinterpret_cast<const void *>
                             (reinterpret_cast<uintptr_t>(chunkAddr) + length);
  
  const void *start = std::max(map->addr, chunkAddr);
  const void *end = std::min(map->end(), chunkEndAddr);
  length = reinterpret_cast<uintptr_t>(end)
           - reinterpret_cast<uintptr_t>(start);

  debug("mprotect @%p, 0x%x, 0x%x", start, length, map->prot);
  if (mprotect(const_cast<void *>(start), length, map->prot) == 0)
    return true;

  log("mprotect failed");
  return false;
}
+82 −0
Original line number Diff line number Diff line
@@ -6,7 +6,9 @@
#define Mappable_h

#include <sys/types.h>
#include <pthread.h>
#include "Zip.h"
#include "SeekableZStream.h"
#include "mozilla/RefPtr.h"
#include "zlib.h"

@@ -155,4 +157,84 @@ private:
  z_stream zStream;
};

/**
 * Mappable implementation for seekable zStreams.
 * Inflates the mapped bits in a temporary buffer, on demand.
 */
class MappableSeekableZStream: public Mappable
{
public:
  ~MappableSeekableZStream();

  /**
   * Create a MappableSeekableZStream instance for the given Zip stream. The
   * name argument is used for an appropriately named temporary file, and the
   * Zip instance is given for the MappableSeekableZStream to keep a reference
   * of it.
   */
  static MappableSeekableZStream *Create(const char *name, Zip *zip,
                                         Zip::Stream *stream);

  /* Inherited from Mappable */
  virtual void *mmap(const void *addr, size_t length, int prot, int flags, off_t offset);
  virtual void munmap(void *addr, size_t length);
  virtual void finalize();
  virtual bool ensure(const void *addr);

private:
  MappableSeekableZStream(Zip *zip);

  /* Zip reference */
  mozilla::RefPtr<Zip> zip;

  /* Decompression buffer */
  AutoDeletePtr<_MappableBuffer> buffer;

  /* Seekable ZStream */
  SeekableZStream zStream;

  /* Keep track of mappings performed with MappableSeekableZStream::mmap so
   * that they can be realized by MappableSeekableZStream::ensure.
   * Values stored in the struct are those passed to mmap */
  struct LazyMap
  {
    const void *addr;
    size_t length;
    int prot;
    off_t offset;

    /* Returns addr + length, as a pointer */
    const void *end() const {
      return reinterpret_cast<const void *>
             (reinterpret_cast<const unsigned char *>(addr) + length);
    }

    /* Returns offset + length */
    const off_t endOffset() const {
      return offset + length;
    }

    /* Returns the offset corresponding to the given address */
    const off_t offsetOf(const void *ptr) const {
      return reinterpret_cast<uintptr_t>(ptr)
             - reinterpret_cast<uintptr_t>(addr) + offset;
    }

    /* Returns whether the given address is in the LazyMap range */
    const bool Contains(const void *ptr) const {
      return (ptr >= addr) && (ptr < end());
    }
  };

  /* List of all mappings */
  std::vector<LazyMap> lazyMaps;

  /* Array keeping track of which chunks have already been decompressed.
   * Each value is the number of pages decompressed for the given chunk. */
  AutoDeleteArray<unsigned char> chunkAvail;

  /* Mutex protecting decompression */
  pthread_mutex_t mutex;
};

#endif /* Mappable_h */
Loading