Added support for dladdr()

dladdr() is a GNU extension function, which allows the caller to retrieve
symbol information for a specified memory address.  It is useful for things
like generating backtrace information at runtime.

Change-Id: I3a1def1a6c9c666d93e1e97b7d260dfa5b9b79a9
diff --git a/libc/include/dlfcn.h b/libc/include/dlfcn.h
index 9582796..f84d1d1 100644
--- a/libc/include/dlfcn.h
+++ b/libc/include/dlfcn.h
@@ -32,10 +32,22 @@
 
 __BEGIN_DECLS
 
+typedef struct {
+    const char *dli_fname;  /* Pathname of shared object that
+                               contains address */
+    void       *dli_fbase;  /* Address at which shared object
+                               is loaded */
+    const char *dli_sname;  /* Name of nearest symbol with address
+                               lower than addr */
+    void       *dli_saddr;  /* Exact address of symbol named
+                               in dli_sname */
+} Dl_info;
+
 extern void*        dlopen(const char*  filename, int flag);
 extern int          dlclose(void*  handle);
 extern const char*  dlerror(void);
 extern void*        dlsym(void*  handle, const char*  symbol);
+extern int          dladdr(void* addr, Dl_info *info);
 
 enum {
   RTLD_NOW  = 0,
diff --git a/libdl/libdl.c b/libdl/libdl.c
index 7971942..b36af16 100644
--- a/libdl/libdl.c
+++ b/libdl/libdl.c
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
+#include <dlfcn.h>
 /* These are stubs for functions that are actually defined
  * in the dynamic linker (dlfcn.c), and hijacked at runtime.
  */
 void *dlopen(const char *filename, int flag) { return 0; }
-char *dlerror(void) { return 0; }
+const char *dlerror(void) { return 0; }
 void *dlsym(void *handle, const char *symbol) { return 0; }
+int dladdr(void *addr, Dl_info *info) { return 0; }
 int dlclose(void *handle) { return 0; }
 
 #ifdef __arm__
diff --git a/linker/dlfcn.c b/linker/dlfcn.c
index 039926c..dcadce5 100644
--- a/linker/dlfcn.c
+++ b/linker/dlfcn.c
@@ -117,6 +117,37 @@
     return 0;
 }
 
+int dladdr(void *addr, Dl_info *info)
+{
+    int ret = 0;
+
+    pthread_mutex_lock(&dl_lock);
+
+    /* Determine if this address can be found in any library currently mapped */
+    soinfo *si = find_containing_library(addr);
+
+    if(si) {
+        memset(info, 0, sizeof(Dl_info));
+
+        info->dli_fname = si->name;
+        info->dli_fbase = (void*)si->base;
+
+        /* Determine if any symbol in the library contains the specified address */
+        Elf32_Sym *sym = find_containing_symbol(addr, si);
+
+        if(sym != NULL) {
+            info->dli_sname = si->strtab + sym->st_name;
+            info->dli_saddr = (void*)(si->base + sym->st_value);
+        }
+
+        ret = 1;
+    }
+
+    pthread_mutex_unlock(&dl_lock);
+
+    return ret;
+}
+
 int dlclose(void *handle)
 {
     pthread_mutex_lock(&dl_lock);
@@ -126,22 +157,22 @@
 }
 
 #if defined(ANDROID_ARM_LINKER)
-//                     0000000 00011111 111112 22222222 233333333334444444444
-//                     0123456 78901234 567890 12345678 901234567890123456789
+//                     0000000 00011111 111112 22222222 2333333 333344444444445555555
+//                     0123456 78901234 567890 12345678 9012345 678901234567890123456
 #define ANDROID_LIBDL_STRTAB \
-                      "dlopen\0dlclose\0dlsym\0dlerror\0dl_unwind_find_exidx\0"
+                      "dlopen\0dlclose\0dlsym\0dlerror\0dladdr\0dl_unwind_find_exidx\0"
 
 #elif defined(ANDROID_X86_LINKER)
-//                     0000000 00011111 111112 22222222 2333333333344444
-//                     0123456 78901234 567890 12345678 9012345678901234
+//                     0000000 00011111 111112 22222222 2333333 3333444444444455
+//                     0123456 78901234 567890 12345678 9012345 6789012345678901
 #define ANDROID_LIBDL_STRTAB \
-                      "dlopen\0dlclose\0dlsym\0dlerror\0dl_iterate_phdr\0"
+                      "dlopen\0dlclose\0dlsym\0dlerror\0dladdr\0dl_iterate_phdr\0"
 
 #elif defined(ANDROID_SH_LINKER)
-//                     0000000 00011111 111112 22222222 2333333333344444
-//                     0123456 78901234 567890 12345678 9012345678901234
+//                     0000000 00011111 111112 22222222 2333333 3333444444444455
+//                     0123456 78901234 567890 12345678 9012345 6789012345678901
 #define ANDROID_LIBDL_STRTAB \
-                      "dlopen\0dlclose\0dlsym\0dlerror\0dl_iterate_phdr\0"
+                      "dlopen\0dlclose\0dlsym\0dlerror\0dladdr\0dl_iterate_phdr\0"
 
 #else /* !defined(ANDROID_ARM_LINKER) && !defined(ANDROID_X86_LINKER) */
 #error Unsupported architecture. Only ARM and x86 are presently supported.
@@ -175,20 +206,25 @@
       st_info: STB_GLOBAL << 4,
       st_shndx: 1,
     },
-#ifdef ANDROID_ARM_LINKER
     { st_name: 29,
+      st_value: (Elf32_Addr) &dladdr,
+      st_info: STB_GLOBAL << 4,
+      st_shndx: 1,
+    },
+#ifdef ANDROID_ARM_LINKER
+    { st_name: 36,
       st_value: (Elf32_Addr) &dl_unwind_find_exidx,
       st_info: STB_GLOBAL << 4,
       st_shndx: 1,
     },
 #elif defined(ANDROID_X86_LINKER)
-    { st_name: 29,
+    { st_name: 36,
       st_value: (Elf32_Addr) &dl_iterate_phdr,
       st_info: STB_GLOBAL << 4,
       st_shndx: 1,
     },
 #elif defined(ANDROID_SH_LINKER)
-    { st_name: 29,
+    { st_name: 36,
       st_value: (Elf32_Addr) &dl_iterate_phdr,
       st_info: STB_GLOBAL << 4,
       st_shndx: 1,
@@ -216,7 +252,7 @@
  * stubbing them out in libdl.
  */
 static unsigned libdl_buckets[1] = { 1 };
-static unsigned libdl_chains[6] = { 0, 2, 3, 4, 5, 0 };
+static unsigned libdl_chains[7] = { 0, 2, 3, 4, 5, 6, 0 };
 
 soinfo libdl_info = {
     name: "libdl.so",
@@ -226,7 +262,7 @@
     symtab: libdl_symtab,
 
     nbucket: 1,
-    nchain: 6,
+    nchain: 7,
     bucket: libdl_buckets,
     chain: libdl_chains,
 };
diff --git a/linker/linker.c b/linker/linker.c
index 6d57cbc..f5294d9 100644
--- a/linker/linker.c
+++ b/linker/linker.c
@@ -538,6 +538,40 @@
     return 0;
 }
 
+soinfo *find_containing_library(void *addr)
+{
+    soinfo *si;
+
+    for(si = solist; si != NULL; si = si->next)
+    {
+        if((unsigned)addr >= si->base && (unsigned)addr - si->base < si->size) {
+            return si;
+        }
+    }
+
+    return NULL;
+}
+
+Elf32_Sym *find_containing_symbol(void *addr, soinfo *si)
+{
+    unsigned int i;
+    unsigned soaddr = (unsigned)addr - si->base;
+
+    /* Search the library's symbol table for any defined symbol which
+     * contains this address */
+    for(i=0; i<si->nchain; i++) {
+        Elf32_Sym *sym = &si->symtab[i];
+
+        if(sym->st_shndx != SHN_UNDEF &&
+           soaddr >= sym->st_value &&
+           soaddr < sym->st_value + sym->st_size) {
+            return sym;
+        }
+    }
+
+    return NULL;
+}
+
 #if 0
 static void dump(soinfo *si)
 {
diff --git a/linker/linker.h b/linker/linker.h
index ec01489..8cd56b0 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -225,6 +225,8 @@
 unsigned unload_library(soinfo *si);
 Elf32_Sym *lookup_in_library(soinfo *si, const char *name);
 Elf32_Sym *lookup(const char *name, soinfo **found);
+soinfo *find_containing_library(void *addr);
+Elf32_Sym *find_containing_symbol(void *addr, soinfo *si);
 const char *linker_get_error(void);
 
 #ifdef ANDROID_ARM_LINKER