Bug 683127 part 8 - Allow to load Elf files from a Zip archive. r=tglek,r=sewardj
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 20 Jan 2012 09:48:44 +0100
changeset 84940 bd752f4935d978c6e81f04c632ea054701f64a08
parent 84939 54a8b1b25477d04ecfae8f13775ec5b25a7c27de
child 84941 229140e62d7b49f34cb140a639d21f92aaa39d97
push idunknown
push userunknown
push dateunknown
reviewerstglek, sewardj
bugs683127
milestone12.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 683127 part 8 - Allow to load Elf files from a Zip archive. r=tglek,r=sewardj
browser/app/Makefile.in
configure.in
mozglue/linker/CustomElf.cpp
mozglue/linker/CustomElf.h
mozglue/linker/ElfLoader.cpp
mozglue/linker/ElfLoader.h
mozglue/linker/Makefile.in
mozglue/linker/Mappable.cpp
mozglue/linker/Mappable.h
mozglue/linker/Utils.h
mozglue/tests/Makefile.in
--- a/browser/app/Makefile.in
+++ b/browser/app/Makefile.in
@@ -78,16 +78,20 @@ LOCAL_INCLUDES += -I$(DEPTH)/build
 DEFINES += -DXPCOM_GLUE
 STL_FLAGS=
 
 LIBS += \
 	$(EXTRA_DSO_LIBS) \
 	$(XPCOM_STANDALONE_GLUE_LDOPTS) \
 	$(NULL)
 
+ifdef MOZ_LINKER
+LIBS += $(ZLIB_LIBS)
+endif
+
 ifndef MOZ_WINCONSOLE
 ifdef MOZ_DEBUG
 MOZ_WINCONSOLE = 1
 else
 MOZ_WINCONSOLE = 0
 endif
 endif
 
--- a/configure.in
+++ b/configure.in
@@ -7154,16 +7154,19 @@ elif test "${OS_TARGET}" = "WINNT" -o "$
   MOZ_GLUE_LDFLAGS='$(call EXPAND_LIBNAME_PATH,mozglue,$(LIBXUL_DIST)/lib)'
 else
   dnl On other Unix systems, we only want to link executables against mozglue
   MOZ_GLUE_PROGRAM_LDFLAGS='$(MKSHLIB_FORCE_ALL) $(call EXPAND_LIBNAME_PATH,mozglue,$(LIBXUL_DIST)/lib) $(MKSHLIB_UNFORCE_ALL)'
   if test -n "$GNU_CC"; then
     dnl And we need mozglue symbols to be exported.
     MOZ_GLUE_PROGRAM_LDFLAGS="$MOZ_GLUE_PROGRAM_LDFLAGS -rdynamic"
   fi
+  if test "$MOZ_LINKER" = 1; then
+    MOZ_GLUE_PROGRAM_LDFLAGS="$MOZ_GLUE_PROGRAM_LDFLAGS $ZLIB_LIBS"
+  fi
 fi
 
 if test -z "$MOZ_MEMORY"; then
   case "${target}" in
     *-mingw*)
       if test -z "$WIN32_REDIST_DIR" -a -z "$MOZ_DEBUG"; then
         AC_MSG_WARN([When not building jemalloc, you need to set WIN32_REDIST_DIR to the path to the Visual C++ Redist (usually VCINSTALLDIR\redist\x86\Microsoft.VC80.CRT, for VC++ v8) if you intend to distribute your build.])
       fi
--- a/mozglue/linker/CustomElf.cpp
+++ b/mozglue/linker/CustomElf.cpp
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <cstring>
 #include <sys/mman.h>
 #include <vector>
 #include <dlfcn.h>
 #include "CustomElf.h"
+#include "Mappable.h"
 #include "Logging.h"
 
 using namespace Elf;
 using namespace mozilla;
 
 #ifndef PAGE_SIZE
 #define PAGE_SIZE 4096
 #endif
@@ -68,28 +69,47 @@ void debug_phdr(const char *type, const 
         "flags: %c%c%c)",
         type, phdr->p_vaddr, phdr->p_filesz, phdr->p_memsz,
         phdr->p_offset, phdr->p_flags & PF_R ? 'r' : '-',
         phdr->p_flags & PF_W ? 'w' : '-', phdr->p_flags & PF_X ? 'x' : '-');
 }
 
 } /* anonymous namespace */
 
+/**
+ * RAII wrapper for a mapping of the first page off a Mappable object.
+ * This calls Mappable::munmap instead of system munmap.
+ */
+class Mappable1stPagePtr: public GenericMappedPtr<Mappable1stPagePtr> {
+public:
+  Mappable1stPagePtr(Mappable *mappable)
+  : GenericMappedPtr<Mappable1stPagePtr>(
+      mappable->mmap(NULL, PAGE_SIZE, PROT_READ, MAP_PRIVATE, 0), PAGE_SIZE)
+  , mappable(mappable)
+  { }
+
+  void munmap(void *buf, size_t length) {
+    mappable->munmap(buf, length);
+  }
+private:
+  Mappable *mappable;
+};
+
+
 TemporaryRef<LibHandle>
-CustomElf::Load(int fd, const char *path, int flags)
+CustomElf::Load(Mappable *mappable, const char *path, int flags)
 {
   debug("CustomElf::Load(\"%s\", %x) = ...", path, flags);
-  if (fd == -1)
+  if (!mappable)
     return NULL;
   /* Keeping a RefPtr of the CustomElf is going to free the appropriate
    * resources when returning NULL */
-  RefPtr<CustomElf> elf = new CustomElf(fd, path);
+  RefPtr<CustomElf> elf = new CustomElf(mappable, path);
   /* Map the first page of the Elf object to access Elf and program headers */
-  MappedPtr ehdr_raw(mmap(NULL, PAGE_SIZE, PROT_READ, MAP_PRIVATE, fd, 0),
-                      PAGE_SIZE);
+  Mappable1stPagePtr ehdr_raw(mappable);
   if (ehdr_raw == MAP_FAILED)
     return NULL;
 
   const Ehdr *ehdr = Ehdr::validate(ehdr_raw);
   if (!ehdr)
     return NULL;
 
   /* Scan Elf Program Headers and gather some information about them */
@@ -164,16 +184,19 @@ CustomElf::Load(int fd, const char *path
   }
 
   /* Load and initialize library */
   for (std::vector<const Phdr *>::iterator it = pt_loads.begin();
        it < pt_loads.end(); ++it)
     if (!elf->LoadSegment(*it))
       return NULL;
 
+  /* We're not going to mmap anymore */
+  mappable->finalize();
+
   report_mapping(const_cast<char *>(elf->GetName()), elf->base,
                  (max_vaddr + PAGE_SIZE - 1) & PAGE_MASK, 0);
 
   if (!elf->InitDyn(dyn))
     return NULL;
 
   debug("CustomElf::Load(\"%s\", %x) = %p", path, flags,
         static_cast<void *>(elf));
@@ -184,16 +207,17 @@ CustomElf::~CustomElf()
 {
   debug("CustomElf::~CustomElf(%p [\"%s\"])",
         reinterpret_cast<void *>(this), GetPath());
   CallFini();
   /* Normally, __cxa_finalize is called by the .fini function. However,
    * Android NDK before r6b doesn't do that. Our wrapped cxa_finalize only
    * calls destructors once, so call it in all cases. */
   ElfLoader::__wrap_cxa_finalize(this);
+  delete mappable;
 }
 
 namespace {
 
 /**
  * Hash function for symbol lookup, as defined in ELF standard for System V
  */
 unsigned long
@@ -334,39 +358,34 @@ CustomElf::LoadSegment(const Phdr *pt_lo
 
   /* Mmap at page boundary */
   Addr page_offset = pt_load->p_vaddr & ~PAGE_MASK;
   void *where = GetPtr(pt_load->p_vaddr - page_offset);
   debug("%s: Loading segment @%p %c%c%c", GetPath(), where,
                                           prot & PROT_READ ? 'r' : '-',
                                           prot & PROT_WRITE ? 'w' : '-',
                                           prot & PROT_EXEC ? 'x' : '-');
-  void *mapped = mmap(where, pt_load->p_filesz + page_offset,
-                      prot, MAP_PRIVATE | MAP_FIXED, fd,
-                      pt_load->p_offset - page_offset);
+  void *mapped = mappable->mmap(where, pt_load->p_filesz + page_offset,
+                                prot, MAP_PRIVATE | MAP_FIXED,
+                                pt_load->p_offset - page_offset);
   if (mapped != where) {
     if (mapped == MAP_FAILED) {
       log("%s: Failed to mmap", GetPath());
     } else {
       log("%s: Didn't map at the expected location (wanted: %p, got: %p)",
           GetPath(), where, mapped);
     }
     return false;
   }
 
   /* When p_memsz is greater than p_filesz, we need to have nulled out memory
    * after p_filesz and before p_memsz.
-   * We first null out bytes after p_filesz and up to the end of the page
-   * p_filesz is in. */
-  Addr end_offset = pt_load->p_filesz + page_offset;
-  if ((prot & PROT_WRITE) && (end_offset & ~PAGE_MASK)) {
-    memset(reinterpret_cast<char *>(mapped) + end_offset,
-           0, PAGE_SIZE - (end_offset & ~PAGE_MASK));
-  }
-  /* Above the end of that page, and up to p_memsz, we already have nulled out
+   * Mappable::mmap already guarantees that after p_filesz and up to the end
+   * of the page p_filesz is in, memory is nulled out.
+   * Above the end of that page, and up to p_memsz, we already have nulled out
    * memory because we mapped anonymous memory on the whole library virtual
    * address space. We just need to adjust this anonymous memory protection
    * flags. */
   if (pt_load->p_memsz > pt_load->p_filesz) {
     Addr file_end = pt_load->p_vaddr + pt_load->p_filesz;
     Addr mem_end = pt_load->p_vaddr + pt_load->p_memsz;
     Addr next_page = (file_end & ~(PAGE_SIZE - 1)) + PAGE_SIZE;
     if (mem_end > next_page) {
--- a/mozglue/linker/CustomElf.h
+++ b/mozglue/linker/CustomElf.h
@@ -197,33 +197,35 @@ struct Rela: public Elf_(Rela)
   Addr GetAddend(void *base) const
   {
     return r_addend;
   }
 };
 
 } /* namespace Elf */
 
+class Mappable;
+
 /**
  * Library Handle class for ELF libraries we don't let the system linker
  * handle.
  */
 class CustomElf: public LibHandle
 {
 public:
   /**
    * Returns a new CustomElf using the given file descriptor to map ELF
    * content. The file descriptor ownership is stolen, and it will be closed
    * in CustomElf's destructor if an instance is created, or by the Load
    * method otherwise. The path corresponds to the file descriptor, and flags
    * are the same kind of flags that would be given to dlopen(), though
    * currently, none are supported and the behaviour is more or less that of
    * RTLD_GLOBAL | RTLD_BIND_NOW.
    */
-  static mozilla::TemporaryRef<LibHandle> Load(int fd,
+  static mozilla::TemporaryRef<LibHandle> Load(Mappable *mappable,
                                                const char *path, int flags);
 
   /**
    * Inherited from LibHandle
    */
   virtual ~CustomElf();
   virtual void *GetSymbolPtr(const char *symbol) const;
   virtual bool Contains(void *addr) const;
@@ -246,18 +248,18 @@ private:
    * given symbol name. This is used to find symbols that are undefined
    * in the Elf object.
    */
   void *GetSymbolPtrInDeps(const char *symbol) const;
 
   /**
    * Private constructor
    */
-  CustomElf(int fd, const char *path)
-  : LibHandle(path), fd(fd), init(0), fini(0), initialized(false)
+  CustomElf(Mappable *mappable, const char *path)
+  : LibHandle(path), mappable(mappable), init(0), fini(0), initialized(false)
   { }
 
   /**
    * Returns a pointer relative to the base address where the library is
    * loaded.
    */
   void *GetPtr(const Elf::Addr offset) const
   {
@@ -328,18 +330,18 @@ private:
   /**
    * Call a function given a an address relative to the library base
    */
   void CallFunction(Elf::Addr addr) const
   {
     return CallFunction(GetPtr(addr));
   }
 
-  /* Appropriated file descriptor */
-  AutoCloseFD fd;
+  /* Appropriated Mappable */
+  Mappable *mappable;
 
   /* Base address where the library is loaded */
   MappedPtr base;
 
   /* String table */
   Elf::Strtab strtab;
 
   /* Symbol table */
--- a/mozglue/linker/ElfLoader.cpp
+++ b/mozglue/linker/ElfLoader.cpp
@@ -5,16 +5,17 @@
 #include <cstring>
 #include <cstdlib>
 #include <dlfcn.h>
 #include <unistd.h>
 #include <algorithm>
 #include <fcntl.h>
 #include "ElfLoader.h"
 #include "CustomElf.h"
+#include "Mappable.h"
 #include "Logging.h"
 
 using namespace mozilla;
 
 /**
  * dlfcn.h replacements functions
  */
 
@@ -184,23 +185,42 @@ ElfLoader::Load(const char *path, int fl
     const char *parentPath = parent->GetPath();
     abs_path = new char[strlen(parentPath) + strlen(path)];
     strcpy(abs_path, parentPath);
     char *slash = strrchr(abs_path, '/');
     strcpy(slash + 1, path);
     path = abs_path;
   }
 
-  /* Try loading the file with the custom linker, and fall back to the
-   * system linker if that fails */
-  AutoCloseFD fd = open(path, O_RDONLY);
-  if (fd != -1) {
-    handle = CustomElf::Load(fd, path, flags);
-    fd.forget();
+  /* Create a mappable object for the given path. Paths in the form
+   *   /foo/bar/baz/archive!/directory/lib.so
+   * try to load the directory/lib.so in /foo/bar/baz/archive, provided
+   * that file is a Zip archive. */
+  Mappable *mappable = NULL;
+  RefPtr<Zip> zip;
+  const char *subpath;
+  if ((subpath = strchr(path, '!'))) {
+    char *zip_path = strndup(path, subpath - path);
+    while (*(++subpath) == '/') { }
+    zip = zips.GetZip(zip_path);
+    Zip::Stream s;
+    if (zip && zip->GetStream(subpath, &s)) {
+      if (s.GetType() == Zip::Stream::DEFLATE)
+        mappable = MappableDeflate::Create(name, zip, &s);
+    }
   }
+  /* If we couldn't load above, try with a MappableFile */
+  if (!mappable && !zip)
+    mappable = MappableFile::Create(path);
+
+  /* Try loading with the custom linker if we have a Mappable */
+  if (mappable)
+    handle = CustomElf::Load(mappable, path, flags);
+
+  /* Try loading with the system linker if everything above failed */
   if (!handle)
     handle = SystemElf::Load(path, flags);
 
   /* If we didn't have an absolute path and haven't been able to load
    * a library yet, try in the system search path */
   if (!handle && abs_path)
     handle = SystemElf::Load(name, flags);
 
--- a/mozglue/linker/ElfLoader.h
+++ b/mozglue/linker/ElfLoader.h
@@ -283,11 +283,14 @@ protected:
     Destructor destructor;
     void *object;
     void *dso_handle;
   };
 
 private:
   /* Keep track of all registered destructors */
   std::vector<DestructorCaller> destructors;
+
+  /* Keep track of Zips used for library loading */
+  ZipCollection zips;
 };
 
 #endif /* ElfLoader_h */
--- a/mozglue/linker/Makefile.in
+++ b/mozglue/linker/Makefile.in
@@ -17,12 +17,13 @@ STL_FLAGS =
 CPPSRCS = \
   Zip.cpp \
   $(NULL)
 
 ifndef MOZ_OLD_LINKER
 CPPSRCS += \
   ElfLoader.cpp \
   CustomElf.cpp \
+  Mappable.cpp \
   $(NULL)
 endif
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/mozglue/linker/Mappable.cpp
@@ -0,0 +1,247 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <cstring>
+#include <cstdlib>
+#include "Mappable.h"
+#ifdef ANDROID
+#include <linux/ashmem.h>
+#endif
+#include "Logging.h"
+
+#ifndef PAGE_SIZE
+#define PAGE_SIZE 4096
+#endif
+
+#ifndef PAGE_MASK
+#define PAGE_MASK (~ (PAGE_SIZE - 1))
+#endif
+
+MappableFile *MappableFile::Create(const char *path)
+{
+  int fd = open(path, O_RDONLY);
+  if (fd != -1)
+    return new MappableFile(fd);
+  return NULL;
+}
+
+void *
+MappableFile::mmap(const void *addr, size_t length, int prot, int flags,
+                   off_t offset)
+{
+  // ASSERT(fd != -1)
+  // ASSERT(! flags & MAP_SHARED)
+  flags |= MAP_PRIVATE;
+
+  void *mapped = ::mmap(const_cast<void *>(addr), length, prot, flags,
+                        fd, offset);
+  if (mapped == MAP_FAILED)
+    return mapped;
+
+  /* Fill the remainder of the last page with zeroes when the requested
+   * protection has write bits. */
+  if ((mapped != MAP_FAILED) && (prot & PROT_WRITE) &&
+      (length & (PAGE_SIZE - 1))) {
+    memset(reinterpret_cast<char *>(mapped) + length, 0,
+           PAGE_SIZE - (length & ~(PAGE_MASK)));
+  }
+  return mapped;
+}
+
+void
+MappableFile::finalize()
+{
+  /* Close file ; equivalent to close(fd.forget()) */
+  fd = -1;
+}
+
+/**
+ * _MappableBuffer is a buffer which content can be mapped at different
+ * locations in the virtual address space.
+ * On Linux, uses a (deleted) temporary file on a tmpfs for sharable content.
+ * On Android, uses ashmem.
+ */
+class _MappableBuffer: public MappedPtr
+{
+public:
+  /**
+   * Returns a _MappableBuffer instance with the given name and the given
+   * length.
+   */
+  static _MappableBuffer *Create(const char *name, size_t length)
+  {
+    AutoCloseFD fd;
+#ifdef ANDROID
+    /* On Android, initialize an ashmem region with the given length */
+    fd = open("/" ASHMEM_NAME_DEF, O_RDWR, 0600);
+    if (fd == -1)
+      return NULL;
+    char str[ASHMEM_NAME_LEN];
+    strlcpy(str, name, sizeof(str));
+    ioctl(fd, ASHMEM_SET_NAME, str);
+    if (ioctl(fd, ASHMEM_SET_SIZE, length))
+      return NULL;
+
+    /* The Gecko crash reporter is confused by adjacent memory mappings of
+     * the same file. On Android, subsequent mappings are growing in memory
+     * address, and chances are we're going to map from the same file
+     * descriptor right away. Allocate one page more than requested so that
+     * there is a gap between this mapping and the subsequent one. */
+    void *buf = ::mmap(NULL, length + PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (buf != MAP_FAILED) {
+      /* Actually create the gap with anonymous memory */
+      ::mmap(reinterpret_cast<char *>(buf) + ((length + PAGE_SIZE) & PAGE_MASK),
+             PAGE_SIZE, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
+             -1, 0);
+      debug("Decompression buffer of size %d in ashmem \"%s\", mapped @%p",
+            length, str, buf);
+      return new _MappableBuffer(fd.forget(), buf, length);
+    }
+#else
+    /* On Linux, use /dev/shm as base directory for temporary files, assuming
+     * it's on tmpfs */
+    /* TODO: check that /dev/shm is tmpfs */
+    char path[256];
+    sprintf(path, "/dev/shm/%s.XXXXXX", name);
+    fd = mkstemp(path);
+    if (fd == -1)
+      return NULL;
+    unlink(path);
+    ftruncate(fd, length);
+
+    void *buf = ::mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (buf != MAP_FAILED) {
+      debug("Decompression buffer of size %ld in \"%s\", mapped @%p",
+            length, path, buf);
+      return new _MappableBuffer(fd.forget(), buf, length);
+    }
+#endif
+    return NULL;
+  }
+
+  void *mmap(const void *addr, size_t length, int prot, int flags, off_t offset)
+  {
+    // ASSERT(fd != -1)
+#ifdef ANDROID
+    /* Mapping ashmem MAP_PRIVATE is like mapping anonymous memory, even when
+     * there is content in the ashmem */
+    if (flags & MAP_PRIVATE) {
+      flags &= ~MAP_PRIVATE;
+      flags |= MAP_SHARED;
+    }
+#endif
+    return ::mmap(const_cast<void *>(addr), length, prot, flags, fd, offset);
+  }
+
+#ifdef ANDROID
+  ~_MappableBuffer() {
+    /* Free the additional page we allocated. See _MappableBuffer::Create */
+    munmap(this + ((GetLength() + PAGE_SIZE) & ~(PAGE_SIZE - 1)), PAGE_SIZE);
+  }
+#endif
+
+private:
+  _MappableBuffer(int fd, void *buf, size_t length)
+  : MappedPtr(buf, length), fd(fd) { }
+
+  /* File descriptor for the temporary file or ashmem */
+  AutoCloseFD fd;
+};
+
+
+MappableDeflate *
+MappableDeflate::Create(const char *name, Zip *zip, Zip::Stream *stream)
+{
+  // ASSERT(stream->GetType() == Zip::Stream::DEFLATE)
+  _MappableBuffer *buf = _MappableBuffer::Create(name, stream->GetUncompressedSize());
+  if (buf)
+    return new MappableDeflate(buf, zip, stream);
+  return NULL;
+}
+
+MappableDeflate::MappableDeflate(_MappableBuffer *buf, Zip *zip,
+                                 Zip::Stream *stream)
+: zip(zip), buffer(buf)
+{
+  /* Initialize Zlib data with zip stream info and decompression buffer */
+  memset(&zStream, 0, sizeof(zStream));
+  zStream.avail_in = stream->GetSize();
+  zStream.next_in = const_cast<Bytef *>(
+                    reinterpret_cast<const Bytef *>(stream->GetBuffer()));
+  zStream.total_in = 0;
+  zStream.avail_out = stream->GetUncompressedSize();
+  zStream.next_out = static_cast<Bytef*>(*buffer);
+  zStream.total_out = 0;
+}
+
+MappableDeflate::~MappableDeflate()
+{
+  delete buffer;
+}
+
+void *
+MappableDeflate::mmap(const void *addr, size_t length, int prot, int flags, off_t offset)
+{
+  // ASSERT(buffer)
+  // ASSERT(! flags & MAP_SHARED)
+  flags |= MAP_PRIVATE;
+
+  /* The deflate stream is uncompressed up to the required offset + length, if
+   * it hasn't previously been uncompressed */
+  ssize_t missing = offset + length + zStream.avail_out - buffer->GetLength();
+  if (missing > 0) {
+    uInt avail_out = zStream.avail_out;
+    zStream.avail_out = missing;
+    if ((*buffer == zStream.next_out) &&
+        (inflateInit2(&zStream, -MAX_WBITS) != Z_OK)) {
+      log("inflateInit failed: %s", zStream.msg);
+      return MAP_FAILED;
+    }
+    int ret = inflate(&zStream, Z_SYNC_FLUSH);
+    if (ret < 0) {
+      log("inflate failed: %s", zStream.msg);
+      return MAP_FAILED;
+    }
+    if (ret == Z_NEED_DICT) {
+      log("zstream requires a dictionary. %s", zStream.msg);
+      return MAP_FAILED;
+    }
+    zStream.avail_out = avail_out - missing + zStream.avail_out;
+    if (ret == Z_STREAM_END) {
+      if (inflateEnd(&zStream) != Z_OK) {
+        log("inflateEnd failed: %s", zStream.msg);
+        return MAP_FAILED;
+      }
+      if (zStream.total_out != buffer->GetLength()) {
+        log("File not fully uncompressed! %ld / %d", zStream.total_out,
+            static_cast<unsigned int>(buffer->GetLength()));
+        return MAP_FAILED;
+      }
+    }
+  }
+#if defined(ANDROID) && defined(__arm__)
+  if (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 + offset, *buffer + (offset + length));
+    cacheflush(reinterpret_cast<uintptr_t>(*buffer + offset),
+               reinterpret_cast<uintptr_t>(*buffer + (offset + length)), 0);
+  }
+#endif
+
+  return buffer->mmap(addr, length, prot, flags, offset);
+}
+
+void
+MappableDeflate::finalize()
+{
+  /* Free decompression buffer */
+  delete buffer;
+  buffer = NULL;
+  /* Remove reference to Zip archive */
+  zip = NULL;
+}
new file mode 100644
--- /dev/null
+++ b/mozglue/linker/Mappable.h
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef Mappable_h
+#define Mappable_h
+
+#include "Zip.h"
+#include "mozilla/RefPtr.h"
+#include "zlib.h"
+
+/**
+ * Abstract class to handle mmap()ing from various kind of entities, such as
+ * plain files or Zip entries. The virtual members are meant to act as the
+ * equivalent system functions, with a few differences:
+ * - mapped memory is always MAP_PRIVATE, even though a given implementation
+ *   may use something different internally.
+ * - memory after length and up to the end of the corresponding page is nulled
+ *   out.
+ */
+class Mappable
+{
+public:
+  virtual ~Mappable() { }
+
+  virtual void *mmap(const void *addr, size_t length, int prot, int flags,
+                     off_t offset) = 0;
+  virtual void munmap(void *addr, size_t length) {
+    ::munmap(addr, length);
+  }
+
+  /**
+   * Indicate to a Mappable instance that no further mmap is going to happen.
+   */
+  virtual void finalize() = 0;
+};
+
+/**
+ * Mappable implementation for plain files
+ */
+class MappableFile: public Mappable
+{
+public:
+  ~MappableFile() { }
+
+  /**
+   * Create a MappableFile instance for the given file path.
+   */
+  static MappableFile *Create(const char *path);
+
+  /* Inherited from Mappable */
+  virtual void *mmap(const void *addr, size_t length, int prot, int flags, off_t offset);
+  virtual void finalize();
+
+private:
+  MappableFile(int fd): fd(fd) { }
+
+  /* File descriptor */
+  AutoCloseFD fd;
+};
+
+class _MappableBuffer;
+
+/**
+ * Mappable implementation for deflated stream in a Zip archive
+ */
+class MappableDeflate: public Mappable
+{
+public:
+  ~MappableDeflate();
+
+  /**
+   * Create a MappableDeflate 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 MappableDeflate to keep a reference of it.
+   */
+  static MappableDeflate *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 finalize();
+
+private:
+  MappableDeflate(_MappableBuffer *buf, Zip *zip, Zip::Stream *stream);
+
+  /* Zip reference */
+  mozilla::RefPtr<Zip> zip;
+
+  /* Decompression buffer */
+  _MappableBuffer *buffer;
+
+  /* Zlib data */
+  z_stream zStream;
+};
+
+#endif /* Mappable_h */
--- a/mozglue/linker/Utils.h
+++ b/mozglue/linker/Utils.h
@@ -91,32 +91,36 @@ public:
 
 private:
   int fd;
 };
 
 /**
  * MappedPtr is a RAII wrapper for mmap()ed memory. It can be used as
  * a simple void * or unsigned char *.
+ *
+ * It is defined as a derivative of a template that allows to use a
+ * different unmapping strategy.
  */
-class MappedPtr
+template <typename T>
+class GenericMappedPtr
 {
 public:
-  MappedPtr(void *buf, size_t length): buf(buf), length(length) { }
-  MappedPtr(): buf(MAP_FAILED), length(0) { }
+  GenericMappedPtr(void *buf, size_t length): buf(buf), length(length) { }
+  GenericMappedPtr(): buf(MAP_FAILED), length(0) { }
 
   void Init(void *b, size_t len) {
     buf = b;
     length = len;
   }
 
-  ~MappedPtr()
+  ~GenericMappedPtr()
   {
     if (buf != MAP_FAILED)
-      munmap(buf, length);
+      static_cast<T *>(this)->munmap(buf, length);
   }
 
   operator void *() const
   {
     return buf;
   }
 
   operator unsigned char *() const
@@ -140,21 +144,41 @@ public:
   /**
    * Returns whether the given address is within the mapped range
    */
   bool Contains(void *ptr) const
   {
     return (ptr >= buf) && (ptr < reinterpret_cast<char *>(buf) + length);
   }
 
+  /**
+   * Returns the length of the mapped range
+   */
+  size_t GetLength() const
+  {
+    return length;
+  }
+
 private:
   void *buf;
   size_t length;
 };
 
+struct MappedPtr: public GenericMappedPtr<MappedPtr>
+{
+  MappedPtr(void *buf, size_t length)
+  : GenericMappedPtr<MappedPtr>(buf, length) { }
+  MappedPtr(): GenericMappedPtr<MappedPtr>() { }
+
+  void munmap(void *buf, size_t length)
+  {
+    ::munmap(buf, length);
+  }
+};
+
 /**
  * UnsizedArray is a way to access raw arrays of data in memory.
  *
  *   struct S { ... };
  *   UnsizedArray<S> a(buf);
  *   UnsizedArray<S> b; b.Init(buf);
  *
  * This is roughly equivalent to
--- a/mozglue/tests/Makefile.in
+++ b/mozglue/tests/Makefile.in
@@ -18,16 +18,18 @@ SIMPLE_PROGRAMS := $(CPPSRCS:.cpp=$(BIN_
 NO_DIST_INSTALL = 1
 STL_FLAGS =
 
 LOCAL_INCLUDES += -I$(srcdir)/../linker
 # Only link against the linker, not mozglue
 MOZ_GLUE_PROGRAM_LDFLAGS =
 MOZ_GLUE_LDFLAGS =
 LIBS += $(call EXPAND_LIBNAME_PATH,linker,../linker)
+
+EXTRA_LIBS = $(ZLIB_LIBS)
 endif
 
 include $(topsrcdir)/config/rules.mk
 
 ifdef MOZ_LINKER
 check::
 	@$(EXIT_ON_ERROR) ./TestZip$(BIN_SUFFIX) $(srcdir)
 endif