changeset 315:3840e0188520

- allowing lookup of running executable's path by passing NULL to dynload's dlGetLibraryPath()
author Tassilo Philipp
date Wed, 06 Nov 2019 14:13:49 +0100
parents b2e4e23d9953
children dc8bbffc39e6
files dynload/dynload.3 dynload/dynload_unix.c
diffstat 2 files changed, 75 insertions(+), 42 deletions(-) [+]
line wrap: on
line diff
--- a/dynload/dynload.3	Wed Nov 06 12:32:53 2019 +0100
+++ b/dynload/dynload.3	Wed Nov 06 14:13:49 2019 +0100
@@ -80,7 +80,9 @@
 .Ar bufSize
 >= return value >= 1, a null-terminted string with the path to the library should be in
 .Ar sOut .
-If it returns 0, the library name wasn't able to be found. Please note that this might happen in some rare cases, so make sure to always check.
+If it returns 0, the library name wasn't able to be found. Please note that this might happen in some rare cases, so make sure to always check. Passing a null pointer as
+.Ar pLib
+returns the path to the executable (not guaranteed to be absolute).
 .Pp
 The dlSyms* functions can be used to iterate over symbols. Since they can be used on libraries that are not linked, they are made
 for symbol name lookups, not to get symbols' addresses. For that refer to
@@ -101,14 +103,22 @@
 .Sh BUGS
 .Fn dlGetLibraryPath
 is not thread-safe on Darwin (macOS, iOS, ...) and OpenBSD.
+.Pp
 .Fn dlSymsInit
 is not thread-safe on Darwin.
+.Pp
 .Fn dlGetLibraryPath
 will not work on the following platforms when the library in question doesn't have the (default)
 .Fn _init
 and
 .Fn _fini
 symbols exported (rare, but possible): Haiku (all versions), OpenBSD < 3.7, NetBSD < 5.1, FreeBSD < 4.8
+.Pp
+Getting the executable's path by passing NULL in
+.Ar pLib
+to
+.Fn dlGetLibraryPath
+fails on the following platforms: Haiku (all versions), OpenBSD < 3.7, NetBSD < 5.1, FreeBSD < 4.8
 .Sh CONFORMING TO
 The dynload library conforms to c99.
 .Ed
--- a/dynload/dynload_unix.c	Wed Nov 06 12:32:53 2019 +0100
+++ b/dynload/dynload_unix.c	Wed Nov 06 14:13:49 2019 +0100
@@ -39,7 +39,7 @@
 #include <string.h>
 
 #if defined(__GLIBC__)
-/* @@@ version check glibc correctly... dl_iterate_phdr(): glibc ver >= 2.2.4*/
+/* @@@ version check glibc more precisely... dl_iterate_phdr(): glibc ver >= 2.2.4*/
 #if (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 3)
 #  define DL_USE_GLIBC_ITER_PHDR
 #endif
@@ -83,11 +83,20 @@
 #endif
 
 
+/* helper copying string if buffer big enough, returning length (without \0) */
+static int dl_strlen_strcpy(char* dst, const char* src, int dstSize)
+{
+  int l = strlen(src);
+  if(l < dstSize) /* l+'\0' <= bufSize */
+    strcpy(dst, src);
+  return l;
+}
+
 /* code for dlGetLibraryPath() is platform specific */
 
 /* if dlinfo() exists use it (except on glibc, where it exists since version
  * 2.3.3, but its implementation is dangerous, as no checks are done whether
- * the handle is valid, thus rendering the returned values useless): check for
+ * the handle is valid, thus rendering the returned values useless) check for
  * RTLD_DI_LINKMAP which is a #define for dlinfo() on most supported targets,
  * or specifically check the OS (e.g. dlinfo() is originally from Solaris) */
 #if (defined(RTLD_DI_LINKMAP) || defined(OS_SunOS)) && !defined(DL_USE_GLIBC_ITER_PHDR)
@@ -98,11 +107,9 @@
 {
   struct link_map* p = NULL;
   int l = -1;
-  if(dlinfo(pLib, RTLD_DI_LINKMAP, &p) == 0 && p) {
-    l = strlen(p->l_name);
-    if(l < bufSize) /* l+'\0' <= bufSize */
-      strcpy(sOut, p->l_name);
-  }
+  if(dlinfo(pLib ? pLib : RTLD_SELF, RTLD_DI_LINKMAP, &p) == 0)
+    l = dl_strlen_strcpy(sOut, p->l_name, bufSize);
+
   return l+1; /* strlen + '\0' */
 }
 
@@ -118,26 +125,30 @@
   uint32_t i;
   int l = -1;
 
-  /*if(pLib == RTLD_DEFAULT)
-    return NULL; @@@ return exec's path */
+  /* request info about own process? lookup first loaded image */
+  if(pLib == NULL) {
+    const char* libPath = _dyld_get_image_name(0); //@@@ consider using _NSGetExecutablePath()
+    if(libPath)
+      l = dl_strlen_strcpy(sOut, libPath, bufSize);
+  }
+  else {
+    /* Darwin's code doesn't come with (non-standard) dlinfo(), so use dyld(1)
+     * code. There doesn't seem to be a direct way to query the library path,
+     * so "double-load" temporarily all already loaded images (just increases
+     * ref count) and compare handles until we found ours. Return the name. */
+    for(i=_dyld_image_count(); i>0;) /* backwards, ours is more likely at end */
+    {
+      const char* libPath = _dyld_get_image_name(--i);
+      void* lib = dlopen(libPath, RTLD_LIGHTEST);
+      if(lib) {
+        dlclose(lib);
 
-  /* Darwin's code doesn't come with (non-standard) dlinfo(), so use dyld(1) */
-  /* code. There doesn't seem to be a direct way to query the library path,  */
-  /* so "double-load" temporarily all already loaded images (just increases  */
-  /* ref count) and compare handles until we found ours. Return the name.    */
-  for(i=_dyld_image_count(); i>0;) /* backwards, ours is more likely at end */
-  {
-    const char* libPath = _dyld_get_image_name(--i);
-    void* lib = dlopen(libPath, RTLD_LIGHTEST);
-    if(lib) {
-      dlclose(lib);
-      /* compare handle pointers' high bits (in low 2 bits some flags might */
-      /* be stored - should be safe b/c address needs alignment, anywas) */
-      if(((intptr_t)pLib ^ (intptr_t)lib) < 4) {
-        l = strlen(libPath);
-        if(l < bufSize) /* l+'\0' <= bufSize */
-          strcpy(sOut, libPath);
-        break;
+        /* compare handle pointers' high bits (in low 2 bits some flags might */
+        /* be stored - should be safe b/c address needs alignment, anyways) */
+        if(((intptr_t)pLib ^ (intptr_t)lib) < 4) {
+          l = dl_strlen_strcpy(sOut, libPath, bufSize);
+          break;
+        }
       }
     }
   }
@@ -164,18 +175,32 @@
 {
   int l = -1;
   iter_phdr_data* d = (iter_phdr_data*)data;
-  /* unable to relate info->dlpi_addr directly to our dlopen handle, let's */
-  /* do what we do on macOS above, re-dlopen the already loaded lib (just  */
-  /* increases ref count) and compare handles. */
-  void* lib = dlopen(info->dlpi_name, RTLD_LIGHTEST);
-  if(lib) {
-    dlclose(lib);
-    if(lib == (void*)d->pLib) {
-      l = strlen(info->dlpi_name);
-      if(l < d->bufSize) /* l+'\0' <= bufSize */
-        strcpy(d->sOut, info->dlpi_name);
+  void* lib = NULL;
+
+  /* get loaded object's handle if not requesting info about process itself */
+  if(d->pLib != NULL) {
+    /* unable to relate info->dlpi_addr directly to our dlopen handle, let's
+     * do what we do on macOS above, re-dlopen the already loaded lib (just
+     * increases ref count) and compare handles */
+    lib = dlopen(info->dlpi_name, RTLD_LIGHTEST);
+    if(lib)
+      dlclose(lib);
+  }
+
+  /* compare handles and get name if found; if d->pLib == NULL this will
+     enter info on first iterated object, which is the process itself */
+  if(lib == (void*)d->pLib) {
+    l = dl_strlen_strcpy(d->sOut, info->dlpi_name, d->bufSize);
+
+    /* on some platforms (e.g. Linux) dlpi_name is empty for the main process'
+       object, but dlpi_addr is given, in that case lookup name via dladdr */
+    if(l == 0 && d->pLib == NULL && (void*)info->dlpi_addr != NULL) {
+      Dl_info i;
+      if(dladdr((void*)info->dlpi_addr, &i) != 0)
+        l = dl_strlen_strcpy(d->sOut, i.dli_fname, d->bufSize);
     }
   }
+
   return l+1; /* strlen + '\0'; is 0 if lib not found, which continues iter */
 }
 
@@ -206,16 +231,14 @@
 
 int dlGetLibraryPath(DLLib* pLib, char* sOut, int bufSize)
 {
+/*@@@ missing handler for pLib == NULL*/
   /* cross fingers that shared object is standard ELF and look for _fini */
   int l = -1;
   void* s = dlsym((void*)pLib, "_fini");
   if(s) {
     Dl_info i;
-    if(dladdr(s, &i) != 0) {
-      l = strlen(i.dli_fname);
-      if(l < bufSize) /* l+'\0' <= bufSize */
-        strcpy(sOut, i.dli_fname);
-    }
+    if(dladdr(s, &i) != 0)
+      l = dl_strlen_strcpy(sOut, i.dli_fname, bufSize);
   }
   return l+1; /* strlen + '\0' */
 }