Bug 683127 part 8 - Allow to load Elf files from a Zip archive. r=tglek,r=sewardj

This commit is contained in:
Mike Hommey 2012-01-20 09:48:44 +01:00
parent 372473a08a
commit c1f8abcc72
11 changed files with 453 additions and 32 deletions

View File

@ -83,6 +83,10 @@ LIBS += \
$(XPCOM_STANDALONE_GLUE_LDOPTS) \
$(NULL)
ifdef MOZ_LINKER
LIBS += $(ZLIB_LIBS)
endif
ifndef MOZ_WINCONSOLE
ifdef MOZ_DEBUG
MOZ_WINCONSOLE = 1

View File

@ -7159,6 +7159,9 @@ else
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

View File

@ -7,6 +7,7 @@
#include <vector>
#include <dlfcn.h>
#include "CustomElf.h"
#include "Mappable.h"
#include "Logging.h"
using namespace Elf;
@ -73,18 +74,37 @@ void debug_phdr(const char *type, const Phdr *phdr)
} /* 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;
@ -169,6 +189,9 @@ CustomElf::Load(int fd, const char *path, int flags)
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);
@ -189,6 +212,7 @@ CustomElf::~CustomElf()
* 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 {
@ -339,9 +363,9 @@ CustomElf::LoadSegment(const Phdr *pt_load) const
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());
@ -354,14 +378,9 @@ CustomElf::LoadSegment(const Phdr *pt_load) const
/* 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. */

View File

@ -202,6 +202,8 @@ struct Rela: public Elf_(Rela)
} /* namespace Elf */
class Mappable;
/**
* Library Handle class for ELF libraries we don't let the system linker
* handle.
@ -218,7 +220,7 @@ public:
* 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);
/**
@ -251,8 +253,8 @@ private:
/**
* 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)
{ }
/**
@ -333,8 +335,8 @@ private:
return CallFunction(GetPtr(addr));
}
/* Appropriated file descriptor */
AutoCloseFD fd;
/* Appropriated Mappable */
Mappable *mappable;
/* Base address where the library is loaded */
MappedPtr base;

View File

@ -10,6 +10,7 @@
#include <fcntl.h>
#include "ElfLoader.h"
#include "CustomElf.h"
#include "Mappable.h"
#include "Logging.h"
using namespace mozilla;
@ -189,13 +190,32 @@ ElfLoader::Load(const char *path, int flags, LibHandle *parent)
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);

View File

@ -288,6 +288,9 @@ protected:
private:
/* Keep track of all registered destructors */
std::vector<DestructorCaller> destructors;
/* Keep track of Zips used for library loading */
ZipCollection zips;
};
#endif /* ElfLoader_h */

View File

@ -22,6 +22,7 @@ ifndef MOZ_OLD_LINKER
CPPSRCS += \
ElfLoader.cpp \
CustomElf.cpp \
Mappable.cpp \
$(NULL)
endif

247
mozglue/linker/Mappable.cpp Normal file
View File

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

96
mozglue/linker/Mappable.h Normal file
View File

@ -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 */

View File

@ -96,22 +96,26 @@ private:
/**
* 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
@ -145,11 +149,31 @@ public:
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.
*

View File

@ -23,6 +23,8 @@ LOCAL_INCLUDES += -I$(srcdir)/../linker
MOZ_GLUE_PROGRAM_LDFLAGS =
MOZ_GLUE_LDFLAGS =
LIBS += $(call EXPAND_LIBNAME_PATH,linker,../linker)
EXTRA_LIBS = $(ZLIB_LIBS)
endif
include $(topsrcdir)/config/rules.mk