Fix dlerror(3).

Add unit tests for dlerror(3) in various situations. I think We're at least
as good as glibc now.

Also factor out the ScopedPthreadMutexLock and use it here too.

Bug: http://code.google.com/p/android/issues/detail?id=38398
Change-Id: I040938b4366ab836e3df46d1d8055b92f4ea6ed8
diff --git a/linker/dlfcn.cpp b/linker/dlfcn.cpp
new file mode 100644
index 0000000..8486bfa
--- /dev/null
+++ b/linker/dlfcn.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dlfcn.h>
+#include <pthread.h>
+#include <stdio.h>
+
+#include <private/ScopedPthreadMutexLocker.h>
+
+#include "linker.h"
+#include "linker_format.h"
+
+/* This file hijacks the symbols stubbed out in libdl.so. */
+
+static char dl_err_buf[1024];
+static const char* dl_err_str;
+
+#define likely(expr)   __builtin_expect (expr, 1)
+#define unlikely(expr) __builtin_expect (expr, 0)
+
+pthread_mutex_t dl_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
+
+static void set_dlerror(const char* msg, const char* detail) {
+  if (detail != NULL) {
+    format_buffer(dl_err_buf, sizeof(dl_err_buf), "%s: %s", msg, detail);
+  } else {
+    format_buffer(dl_err_buf, sizeof(dl_err_buf), "%s", msg);
+  }
+  dl_err_str = (const char*) &dl_err_buf[0];
+}
+
+void *dlopen(const char* filename, int flag) {
+  ScopedPthreadMutexLocker locker(&dl_lock);
+  soinfo* result = find_library(filename);
+  if (result == NULL) {
+    set_dlerror("dlopen failed", linker_get_error());
+    return NULL;
+  }
+  soinfo_call_constructors(result);
+  result->refcount++;
+  return result;
+}
+
+const char* dlerror() {
+  const char* old_value = dl_err_str;
+  dl_err_str = NULL;
+  return (const char*) old_value;
+}
+
+void* dlsym(void* handle, const char* symbol) {
+  ScopedPthreadMutexLocker locker(&dl_lock);
+
+  if (unlikely(handle == 0)) {
+    set_dlerror("dlsym library handle is null", NULL);
+    return NULL;
+  }
+  if (unlikely(symbol == 0)) {
+    set_dlerror("dlsym symbol name is null", NULL);
+    return NULL;
+  }
+
+  soinfo* found = NULL;
+  Elf32_Sym* sym = NULL;
+  if (handle == RTLD_DEFAULT) {
+    sym = lookup(symbol, &found, NULL);
+  } else if (handle == RTLD_NEXT) {
+    void* ret_addr = __builtin_return_address(0);
+    soinfo* si = find_containing_library(ret_addr);
+
+    sym = NULL;
+    if (si && si->next) {
+      sym = lookup(symbol, &found, si->next);
+    }
+  } else {
+    found = (soinfo*) handle;
+    sym = soinfo_lookup(found, symbol);
+  }
+
+  if (likely(sym != 0)) {
+    unsigned bind = ELF32_ST_BIND(sym->st_info);
+
+    if (likely((bind == STB_GLOBAL) && (sym->st_shndx != 0))) {
+      unsigned ret = sym->st_value + found->load_bias;
+      return (void*) ret;
+    }
+
+    set_dlerror("symbol found but not global", symbol);
+    return NULL;
+  } else {
+    set_dlerror("undefined symbol", symbol);
+    return NULL;
+  }
+}
+
+int dladdr(const void* addr, Dl_info* info) {
+  ScopedPthreadMutexLocker locker(&dl_lock);
+
+  // Determine if this address can be found in any library currently mapped.
+  soinfo* si = find_containing_library(addr);
+  if (si == NULL) {
+    return 0;
+  }
+
+  memset(info, 0, sizeof(Dl_info));
+
+  info->dli_fname = si->name;
+  // Address at which the shared object is loaded.
+  info->dli_fbase = (void*) si->base;
+
+  // Determine if any symbol in the library contains the specified address.
+  Elf32_Sym *sym = soinfo_find_symbol(si, addr);
+  if (sym != NULL) {
+    info->dli_sname = si->strtab + sym->st_name;
+    info->dli_saddr = (void*)(si->load_bias + sym->st_value);
+  }
+
+  return 1;
+}
+
+int dlclose(void* handle) {
+  ScopedPthreadMutexLocker locker(&dl_lock);
+  return soinfo_unload((soinfo*) handle);
+}
+
+#if defined(ANDROID_ARM_LINKER)
+//                     0000000 00011111 111112 22222222 2333333 333344444444445555555
+//                     0123456 78901234 567890 12345678 9012345 678901234567890123456
+#define ANDROID_LIBDL_STRTAB \
+                      "dlopen\0dlclose\0dlsym\0dlerror\0dladdr\0dl_unwind_find_exidx\0"
+
+#elif defined(ANDROID_X86_LINKER) || defined(ANDROID_MIPS_LINKER)
+//                     0000000 00011111 111112 22222222 2333333 3333444444444455
+//                     0123456 78901234 567890 12345678 9012345 6789012345678901
+#define ANDROID_LIBDL_STRTAB \
+                      "dlopen\0dlclose\0dlsym\0dlerror\0dladdr\0dl_iterate_phdr\0"
+#else
+#error Unsupported architecture. Only ARM, MIPS, and x86 are presently supported.
+#endif
+
+// name_offset: starting index of the name in libdl_info.strtab
+#define ELF32_SYM_INITIALIZER(name_offset, value, shndx) \
+    { name_offset, \
+      reinterpret_cast<Elf32_Addr>(reinterpret_cast<void*>(value)), \
+      /* st_size */ 0, \
+      (shndx == 0) ? 0 : (STB_GLOBAL << 4), \
+      /* st_other */ 0, \
+      shndx }
+
+static Elf32_Sym libdl_symtab[] = {
+  // Total length of libdl_info.strtab, including trailing 0.
+  // This is actually the STH_UNDEF entry. Technically, it's
+  // supposed to have st_name == 0, but instead, it points to an index
+  // in the strtab with a \0 to make iterating through the symtab easier.
+  ELF32_SYM_INITIALIZER(sizeof(ANDROID_LIBDL_STRTAB) - 1, NULL, 0),
+  ELF32_SYM_INITIALIZER( 0, &dlopen, 1),
+  ELF32_SYM_INITIALIZER( 7, &dlclose, 1),
+  ELF32_SYM_INITIALIZER(15, &dlsym, 1),
+  ELF32_SYM_INITIALIZER(21, &dlerror, 1),
+  ELF32_SYM_INITIALIZER(29, &dladdr, 1),
+#if defined(ANDROID_ARM_LINKER)
+  ELF32_SYM_INITIALIZER(36, &dl_unwind_find_exidx, 1),
+#elif defined(ANDROID_X86_LINKER) || defined(ANDROID_MIPS_LINKER)
+  ELF32_SYM_INITIALIZER(36, &dl_iterate_phdr, 1),
+#endif
+};
+
+/* Fake out a hash table with a single bucket.
+ * A search of the hash table will look through
+ * libdl_symtab starting with index [1], then
+ * use libdl_chains to find the next index to
+ * look at.  libdl_chains should be set up to
+ * walk through every element in libdl_symtab,
+ * and then end with 0 (sentinel value).
+ *
+ * I.e., libdl_chains should look like
+ * { 0, 2, 3, ... N, 0 } where N is the number
+ * of actual symbols, or nelems(libdl_symtab)-1
+ * (since the first element of libdl_symtab is not
+ * a real symbol).
+ *
+ * (see _elf_lookup())
+ *
+ * Note that adding any new symbols here requires
+ * stubbing them out in libdl.
+ */
+static unsigned libdl_buckets[1] = { 1 };
+static unsigned libdl_chains[7] = { 0, 2, 3, 4, 5, 6, 0 };
+
+soinfo libdl_info = {
+    name: "libdl.so",
+
+    phdr: 0, phnum: 0,
+    entry: 0, base: 0, size: 0,
+    unused: 0, dynamic: 0, unused2: 0, unused3: 0,
+    next: 0,
+
+    flags: FLAG_LINKED,
+
+    strtab: ANDROID_LIBDL_STRTAB,
+    symtab: libdl_symtab,
+
+    nbucket: 1,
+    nchain: 7,
+    bucket: libdl_buckets,
+    chain: libdl_chains,
+
+    plt_got: 0, plt_rel: 0, plt_rel_count: 0, rel: 0, rel_count: 0,
+    preinit_array: 0, preinit_array_count: 0, init_array: 0, init_array_count: 0,
+    fini_array: 0, fini_array_count: 0, init_func: 0, fini_func: 0,
+
+#if defined(ANDROID_ARM_LINKER)
+    ARM_exidx: 0, ARM_exidx_count: 0,
+#elif defined(ANDROID_MIPS_LINKER)
+    mips_symtabno: 0, mips_local_gotno: 0, mips_gotsym: 0,
+#endif
+
+    refcount: 0,
+    { l_addr: 0, l_name: 0, l_ld: 0, l_next: 0, l_prev: 0, },
+    constructors_called: 0, load_bias: 0, has_text_relocations: 0,
+};