Bug 683127 part 5 - Initial Elf Loader, wrapping around dlopen/dladdr/dlsym/dlclose. r=tglek,r=sewardj
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 20 Jan 2012 09:48:39 +0100
changeset 84975 dd3b6608192433b823543fe180aebd8ba61f963b
parent 84974 fd1fd139aa1645414103503309460a1567cd7e77
child 84976 fe5a5abec7ed9d6546e5b715ec4ba009f119185a
push id459
push userrcampbell@mozilla.com
push dateSat, 21 Jan 2012 15:34:19 +0000
treeherderfx-team@d43360499b86 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstglek, sewardj
bugs683127
milestone12.0a1
Bug 683127 part 5 - Initial Elf Loader, wrapping around dlopen/dladdr/dlsym/dlclose. r=tglek,r=sewardj
mozglue/linker/ElfLoader.cpp
mozglue/linker/ElfLoader.h
mozglue/linker/Makefile.in
new file mode 100644
--- /dev/null
+++ b/mozglue/linker/ElfLoader.cpp
@@ -0,0 +1,275 @@
+/* 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 <cstring>
+#include <cstdlib>
+#include <dlfcn.h>
+#include <unistd.h>
+#include <algorithm>
+#include "ElfLoader.h"
+#include "Logging.h"
+
+using namespace mozilla;
+
+/**
+ * dlfcn.h replacements functions
+ */
+
+void *
+__wrap_dlopen(const char *path, int flags)
+{
+  RefPtr<LibHandle> handle = ElfLoader::Singleton.Load(path, flags);
+  if (handle)
+    handle->AddDirectRef();
+  return handle;
+}
+
+const char *
+__wrap_dlerror(void)
+{
+  const char *error = ElfLoader::Singleton.lastError;
+  ElfLoader::Singleton.lastError = NULL;
+  return error;
+}
+
+void *
+__wrap_dlsym(void *handle, const char *symbol)
+{
+  if (!handle) {
+    ElfLoader::Singleton.lastError = "dlsym(NULL, sym) unsupported";
+    return NULL;
+  }
+  if (handle != RTLD_DEFAULT && handle != RTLD_NEXT) {
+    LibHandle *h = reinterpret_cast<LibHandle *>(handle);
+    return h->GetSymbolPtr(symbol);
+  }
+  return dlsym(handle, symbol);
+}
+
+int
+__wrap_dlclose(void *handle)
+{
+  if (!handle) {
+    ElfLoader::Singleton.lastError = "No handle given to dlclose()";
+    return -1;
+  }
+  reinterpret_cast<LibHandle *>(handle)->ReleaseDirectRef();
+  return 0;
+}
+
+int
+__wrap_dladdr(void *addr, Dl_info *info)
+{
+  RefPtr<LibHandle> handle = ElfLoader::Singleton.GetHandleByPtr(addr);
+  if (!handle)
+    return 0;
+  info->dli_fname = handle->GetPath();
+  return 1;
+}
+
+namespace {
+
+/**
+ * Returns the part after the last '/' for the given path
+ */
+const char *
+LeafName(const char *path)
+{
+  const char *lastSlash = strrchr(path, '/');
+  if (lastSlash)
+    return lastSlash + 1;
+  return path;
+}
+
+} /* Anonymous namespace */
+
+/**
+ * LibHandle
+ */
+LibHandle::~LibHandle()
+{
+  ElfLoader::Singleton.Forget(this);
+  free(path);
+}
+
+const char *
+LibHandle::GetName() const
+{
+  return path ? LeafName(path) : NULL;
+}
+
+/**
+ * SystemElf
+ */
+TemporaryRef<LibHandle>
+SystemElf::Load(const char *path, int flags)
+{
+  /* The Android linker returns a handle when the file name matches an
+   * already loaded library, even when the full path doesn't exist */
+  if (path && path[0] == '/' && (access(path, F_OK) == -1)){
+    debug("dlopen(\"%s\", %x) = %p", path, flags, (void *)NULL);
+    return NULL;
+  }
+
+  void *handle = dlopen(path, flags);
+  debug("dlopen(\"%s\", %x) = %p", path, flags, handle);
+  ElfLoader::Singleton.lastError = dlerror();
+  if (handle)
+    return new SystemElf(path, handle);
+  return NULL;
+}
+
+SystemElf::~SystemElf()
+{
+  if (!dlhandle)
+    return;
+  debug("dlclose(%p [\"%s\"])", dlhandle, GetPath());
+  dlclose(dlhandle);
+  ElfLoader::Singleton.lastError = dlerror();
+}
+
+void *
+SystemElf::GetSymbolPtr(const char *symbol) const
+{
+  void *sym = dlsym(dlhandle, symbol);
+  debug("dlsym(%p [\"%s\"], \"%s\") = %p", dlhandle, GetPath(), symbol, sym);
+  ElfLoader::Singleton.lastError = dlerror();
+  return sym;
+}
+
+/**
+ * ElfLoader
+ */
+
+/* Unique ElfLoader instance */
+ElfLoader ElfLoader::Singleton;
+
+TemporaryRef<LibHandle>
+ElfLoader::Load(const char *path, int flags, LibHandle *parent)
+{
+  RefPtr<LibHandle> handle;
+
+  /* Handle dlopen(NULL) directly. */
+  if (!path) {
+    handle = SystemElf::Load(NULL, flags);
+    handles.push_back(handle);
+    return handle;
+  }
+
+  /* TODO: Handle relative paths correctly */
+  const char *name = LeafName(path);
+
+  /* Search the list of handles we already have for a match. When the given
+   * path is not absolute, compare file names, otherwise compare full paths. */
+  if (name == path) {
+    for (LibHandleList::iterator it = handles.begin(); it < handles.end(); ++it)
+      if ((*it)->GetName() && (strcmp((*it)->GetName(), name) == 0))
+        return *it;
+  } else {
+    for (LibHandleList::iterator it = handles.begin(); it < handles.end(); ++it)
+      if ((*it)->GetPath() && (strcmp((*it)->GetPath(), path) == 0))
+        return *it;
+  }
+
+  char *abs_path = NULL;
+
+  /* When the path is not absolute and the library is being loaded for
+   * another, first try to load the library from the directory containing
+   * that parent library. */
+  if ((name == path) && parent) {
+    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;
+  }
+
+  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);
+
+  delete [] abs_path;
+  debug("ElfLoader::Load(\"%s\", 0x%x, %p [\"%s\"]) = %p", path, flags,
+        reinterpret_cast<void *>(parent), parent ? parent->GetPath() : "",
+        static_cast<void *>(handle));
+
+  /* Bookkeeping */
+  if (handle)
+    handles.push_back(handle);
+
+  return handle;
+}
+
+mozilla::TemporaryRef<LibHandle>
+ElfLoader::GetHandleByPtr(void *addr)
+{
+  /* Scan the list of handles we already have for a match */
+  for (LibHandleList::iterator it = handles.begin(); it < handles.end(); ++it) {
+    if ((*it)->Contains(addr))
+      return *it;
+  }
+  return NULL;
+}
+
+void
+ElfLoader::Forget(LibHandle *handle)
+{
+  LibHandleList::iterator it = std::find(handles.begin(), handles.end(), handle);
+  if (it != handles.end()) {
+    debug("ElfLoader::Forget(%p [\"%s\"])", reinterpret_cast<void *>(handle),
+                                            handle->GetPath());
+    handles.erase(it);
+  } else {
+    debug("ElfLoader::Forget(%p [\"%s\"]): Handle not found",
+          reinterpret_cast<void *>(handle), handle->GetPath());
+  }
+}
+
+ElfLoader::~ElfLoader()
+{
+  LibHandleList list;
+  /* Build up a list of all library handles with direct (external) references.
+   * We actually skip system library handles because we want to keep at least
+   * some of these open. Most notably, Mozilla codebase keeps a few libgnome
+   * libraries deliberately open because of the mess that libORBit destruction
+   * is. dlclose()ing these libraries actually leads to problems. */
+  for (LibHandleList::reverse_iterator it = handles.rbegin();
+       it < handles.rend(); ++it) {
+    if ((*it)->DirectRefCount()) {
+      if ((*it)->IsSystemElf()) {
+        static_cast<SystemElf *>(*it)->Forget();
+      } else {
+        list.push_back(*it);
+      }
+    }
+  }
+  /* Force release all external references to the handles collected above */
+  for (LibHandleList::iterator it = list.begin(); it < list.end(); ++it) {
+    while ((*it)->ReleaseDirectRef()) { }
+  }
+  /* Remove the remaining system handles. */
+  if (handles.size()) {
+    list = handles;
+    for (LibHandleList::reverse_iterator it = list.rbegin();
+         it < list.rend(); ++it) {
+      if ((*it)->IsSystemElf()) {
+        debug("ElfLoader::~ElfLoader(): Remaining handle for \"%s\" "
+              "[%d direct refs, %d refs total]", (*it)->GetPath(),
+              (*it)->DirectRefCount(), (*it)->refCount());
+        delete (*it);
+      } else {
+        debug("ElfLoader::~ElfLoader(): Unexpected remaining handle for \"%s\" "
+              "[%d direct refs, %d refs total]", (*it)->GetPath(),
+              (*it)->DirectRefCount(), (*it)->refCount());
+        /* Not removing, since it could have references to other libraries,
+         * destroying them as a side effect, and possibly leaving dangling
+         * pointers in the handle list we're scanning */
+      }
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mozglue/linker/ElfLoader.h
@@ -0,0 +1,227 @@
+/* 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 ElfLoader_h
+#define ElfLoader_h
+
+#include <vector>
+/* Until RefPtr.h stops using JS_Assert */
+#undef DEBUG
+#include "mozilla/RefPtr.h"
+
+/**
+ * dlfcn.h replacement functions
+ */
+extern "C" {
+  void *__wrap_dlopen(const char *path, int flags);
+  const char *__wrap_dlerror(void);
+  void *__wrap_dlsym(void *handle, const char *symbol);
+  int __wrap_dlclose(void *handle);
+
+#ifndef HAVE_DLADDR
+  typedef struct {
+    const char *dli_fname;
+    void *dli_fbase;
+    const char *dli_sname;
+    void *dli_saddr;
+  } Dl_info;
+#endif
+  int __wrap_dladdr(void *addr, Dl_info *info);
+}
+
+/**
+ * Abstract class for loaded libraries. Libraries may be loaded through the
+ * system linker or this linker, both cases will be derived from this class.
+ */
+class LibHandle: public mozilla::RefCounted<LibHandle>
+{
+public:
+  /**
+   * Constructor. Takes the path of the loaded library and will store a copy
+   * of the leaf name.
+   */
+  LibHandle(const char *path)
+  : directRefCnt(0), path(path ? strdup(path) : NULL) { }
+
+  /**
+   * Destructor.
+   */
+  virtual ~LibHandle();
+
+  /**
+   * Returns the pointer to the address to which the given symbol resolves
+   * inside the library. It is not supposed to resolve the symbol in other
+   * libraries, although in practice, it will for system libraries.
+   */
+  virtual void *GetSymbolPtr(const char *symbol) const = 0;
+
+  /**
+   * Returns whether the given address is part of the virtual address space
+   * covered by the loaded library.
+   */
+  virtual bool Contains(void *addr) const = 0;
+
+  /**
+   * Returns the file name of the library without the containing directory.
+   */
+  const char *GetName() const;
+
+  /**
+   * Returns the full path of the library, when available. Otherwise, returns
+   * the file name.
+   */
+  const char *GetPath() const
+  {
+    return path;
+  }
+
+  /**
+   * Library handles can be referenced from other library handles or
+   * externally (when dlopen()ing using this linker). We need to be
+   * able to distinguish between the two kind of referencing for better
+   * bookkeeping.
+   */
+  void AddDirectRef()
+  {
+    ++directRefCnt;
+    mozilla::RefCounted<LibHandle>::AddRef();
+  }
+
+  /**
+   * Releases a direct reference, and returns whether there are any direct
+   * references left.
+   */
+  bool ReleaseDirectRef()
+  {
+    bool ret = false;
+    if (directRefCnt) {
+      // ASSERT(directRefCnt >= mozilla::RefCounted<LibHandle>::refCount())
+      if (--directRefCnt)
+        ret = true;
+      mozilla::RefCounted<LibHandle>::Release();
+    }
+    return ret;
+  }
+
+  /**
+   * Returns the number of direct references
+   */
+  int DirectRefCount()
+  {
+    return directRefCnt;
+  }
+
+protected:
+  /**
+   * Returns whether the handle is a SystemElf or not. (short of a better way
+   * to do this without RTTI)
+   */
+  friend class ElfLoader;
+  virtual bool IsSystemElf() const { return false; }
+
+private:
+  int directRefCnt;
+  char *path;
+};
+
+/**
+ * Class handling libraries loaded by the system linker
+ */
+class SystemElf: public LibHandle
+{
+public:
+  /**
+   * Returns a new SystemElf for the given path. The given flags are passed
+   * to dlopen().
+   */
+  static mozilla::TemporaryRef<LibHandle> Load(const char *path, int flags);
+
+  /**
+   * Inherited from LibHandle
+   */
+  virtual ~SystemElf();
+  virtual void *GetSymbolPtr(const char *symbol) const;
+  virtual bool Contains(void *addr) const { return false; /* UNIMPLEMENTED */ }
+
+protected:
+  /**
+   * Returns whether the handle is a SystemElf or not. (short of a better way
+   * to do this without RTTI)
+   */
+  friend class ElfLoader;
+  virtual bool IsSystemElf() const { return true; }
+
+  /**
+   * Remove the reference to the system linker handle. This avoids dlclose()
+   * being called when the instance is destroyed.
+   */
+  void Forget()
+  {
+    dlhandle = NULL;
+  }
+
+private:
+  /**
+   * Private constructor
+   */
+  SystemElf(const char *path, void *handle)
+  : LibHandle(path), dlhandle(handle) { }
+
+  /* Handle as returned by system dlopen() */
+  void *dlhandle;
+};
+
+/**
+ * Elf Loader class in charge of loading and bookkeeping libraries.
+ */
+class ElfLoader
+{
+public:
+  /**
+   * The Elf Loader instance
+   */
+  static ElfLoader Singleton;
+
+  /**
+   * Loads the given library with the given flags. Equivalent to dlopen()
+   * The extra "parent" argument optionally gives the handle of the library
+   * requesting the given library to be loaded. The loader may look in the
+   * directory containing that parent library for the library to load.
+   */
+  mozilla::TemporaryRef<LibHandle> Load(const char *path, int flags,
+                                        LibHandle *parent = NULL);
+
+  /**
+   * Returns the handle of the library containing the given address in
+   * its virtual address space, i.e. the library handle for which
+   * LibHandle::Contains returns true. Its purpose is to allow to
+   * implement dladdr().
+   */
+  mozilla::TemporaryRef<LibHandle> GetHandleByPtr(void *addr);
+
+protected:
+  /**
+   * Forget about the given handle. This method is meant to be called by
+   * the LibHandle destructor.
+   */
+  friend LibHandle::~LibHandle();
+  void Forget(LibHandle *handle);
+
+  /* Last error. Used for dlerror() */
+  friend class SystemElf;
+  friend const char *__wrap_dlerror(void);
+  friend void *__wrap_dlsym(void *handle, const char *symbol);
+  friend int __wrap_dlclose(void *handle);
+  const char *lastError;
+
+private:
+  ElfLoader() { }
+  ~ElfLoader();
+
+  /* Bookkeeping */
+  typedef std::vector<LibHandle *> LibHandleList;
+  LibHandleList handles;
+};
+
+#endif /* ElfLoader_h */
--- a/mozglue/linker/Makefile.in
+++ b/mozglue/linker/Makefile.in
@@ -13,9 +13,15 @@ MODULE		= mozglue
 LIBRARY_NAME	= linker
 FORCE_STATIC_LIB= 1
 STL_FLAGS =
 
 CPPSRCS = \
   Zip.cpp \
   $(NULL)
 
+ifndef MOZ_OLD_LINKER
+CPPSRCS += \
+  ElfLoader.cpp \
+  $(NULL)
+endif
+
 include $(topsrcdir)/config/rules.mk