rtld: Use DAGS for symbol lookup; adjust weak symbol logic
authorJohn Marino <draco@marino.st>
Sat, 21 Jan 2012 21:35:24 +0000 (22:35 +0100)
committerJohn Marino <draco@marino.st>
Mon, 23 Jan 2012 16:13:18 +0000 (17:13 +0100)
get_program_var_addr() must prefer the strong symbol to the weak one.
Search global objects, together with main object and dependencies for the
requested symbol.

Move the common code from symlook_default() into new helper
symlook_global(), and use it both in symlook_global() and
get_program_var_addr().  Supply lock state to get_program_var_addr().

Taken-from: FreeBSD SVN 217223 (10 JAN 2011)

Eliminate the use of symlook_needed function in favor of DAGS.  Place
elements on DAG lists in breadth-first order. This allows us to walk
pre-built list in all cases where breadth-first dependency chain
enumeration is required.

Fix dlsym on special handle obtained by dlopen(NULL, ...) to do what
comment claims it does. Take advantage of recently added symlook_global
function to iterate over main objects and global DAGs lists properly in
search of a symbol. Since rtld itself provides part of the global
namespace, search rtld_obj too.

Remove recursion from init_dag and symlook_needed functions. Use
symlook_needed for ELF filtee processing only and change lookup order
used in the function to match the order used by Solaris runtime linker
under same circumstances. While there, fix weak symbol handling in the
loop so that we return the first weak symbol definition if no strong one
was found, instead of the last one.

Taken-from: FreeBSD SVN 218051 (28 JAN 2011)

libexec/rtld-elf/rtld.c

index cb80a31..49afbb4 100644 (file)
@@ -94,7 +94,6 @@ static void *fill_search_info(const char *, size_t, void *);
 static char *find_library(const char *, const Obj_Entry *);
 static const char *gethints(void);
 static void init_dag(Obj_Entry *);
-static void init_dag1(Obj_Entry *, Obj_Entry *, DoneList *);
 static void init_rtld(caddr_t, Elf_Auxinfo **);
 static void initlist_add_neededs(Needed_Entry *, Objlist *);
 static void initlist_add_objects(Obj_Entry *, Obj_Entry **, Objlist *);
@@ -122,9 +121,10 @@ static int rtld_dirname(const char *, char *);
 static int rtld_dirname_abs(const char *, char *);
 static void rtld_exit(void);
 static char *search_library_path(const char *, const char *);
-static const void **get_program_var_addr(const char *);
+static const void **get_program_var_addr(const char *, RtldLockState *);
 static void set_program_var(const char *, const void *);
 static int symlook_default(SymLook *, const Obj_Entry *refobj);
+static int symlook_global(SymLook *, DoneList *);
 static void symlook_init_from_req(SymLook *, const SymLook *);
 static int symlook_list(SymLook *, const Objlist *, DoneList *);
 static int symlook_needed(SymLook *, const Needed_Entry *, DoneList *);
@@ -1472,28 +1472,33 @@ gethints(void)
 static void
 init_dag(Obj_Entry *root)
 {
+    const Needed_Entry *needed;
+    const Objlist_Entry *elm;
     DoneList donelist;
 
     if (root->dag_inited)
        return;
     donelist_init(&donelist);
-    init_dag1(root, root, &donelist);
-    root->dag_inited = true;
-}
-
-static void
-init_dag1(Obj_Entry *root, Obj_Entry *obj, DoneList *dlp)
-{
-    const Needed_Entry *needed;
 
-    if (donelist_check(dlp, obj))
-       return;
+    /* Root object belongs to own DAG. */
+    objlist_push_tail(&root->dldags, root);
+    objlist_push_tail(&root->dagmembers, root);
+    donelist_check(&donelist, root);
 
-    objlist_push_tail(&obj->dldags, root);
-    objlist_push_tail(&root->dagmembers, obj);
-    for (needed = obj->needed;  needed != NULL;  needed = needed->next)
-       if (needed->obj != NULL)
-           init_dag1(root, needed->obj, dlp);
+    /*
+     * Add dependencies of root object to DAG in breadth order
+     * by exploiting the fact that each new object get added
+     * to the tail of the dagmembers list.
+     */
+    STAILQ_FOREACH(elm, &root->dagmembers, link) {
+       for (needed = elm->obj->needed; needed != NULL; needed = needed->next) {
+           if (needed->obj == NULL || donelist_check(&donelist, needed->obj))
+               continue;
+           objlist_push_tail(&needed->obj->dldags, root);
+           objlist_push_tail(&root->dagmembers, needed->obj);
+       }
+    }
+    root->dag_inited = true;
 }
 
 /*
@@ -2287,12 +2292,18 @@ dlerror(void)
 void *
 dlopen(const char *name, int mode)
 {
+    RtldLockState lockstate;
     int lo_flags;
 
     LD_UTRACE(UTRACE_DLOPEN_START, NULL, NULL, 0, mode, name);
     ld_tracing = (mode & RTLD_TRACE) == 0 ? NULL : "1";
-    if (ld_tracing != NULL)
-       environ = (char **)*get_program_var_addr("environ");
+    if (ld_tracing != NULL) {
+       rlock_acquire(rtld_bind_lock, &lockstate);
+       if (sigsetjmp(lockstate.env, 0) != 0)
+           lock_upgrade(rtld_bind_lock, &lockstate);
+       environ = (char **)*get_program_var_addr("environ", &lockstate);
+       lock_release(rtld_bind_lock, &lockstate);
+    }
     lo_flags = RTLD_LO_DLOPEN;
     if (mode & RTLD_NODELETE)
            lo_flags |= RTLD_LO_NODELETE;
@@ -2470,32 +2481,28 @@ do_dlsym(void *handle, const char *name, void *retaddr, const Ver_Entry *ve,
 
        donelist_init(&donelist);
        if (obj->mainprog) {
-           /* Search main program and all libraries loaded by it. */
-           res = symlook_list(&req, &list_main, &donelist);
+            /* Handle obtained by dlopen(NULL, ...) implies global scope. */
+           res = symlook_global(&req, &donelist);
            if (res == 0) {
                def = req.sym_out;
                defobj = req.defobj_out;
-           } else {
+           }
            /*
-                * We do not distinguish between 'main' object and
-                * global scope.  If symbol is not defined by objects
-                * loaded at startup, continue search among
-                * dynamically loaded objects with RTLD_GLOBAL scope.
+            * Search the dynamic linker itself, and possibly resolve the
+            * symbol from there.  This is how the application links to
+            * dynamic linker services such as dlopen.
             */
-               res = symlook_list(&req, &list_global, &donelist);
+           if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) {
+               res = symlook_obj(&req, &obj_rtld);
                if (res == 0) {
                    def = req.sym_out;
                    defobj = req.defobj_out;
                }
            }
-       } else {
-           Needed_Entry fake;
-
+       }
+       else {
            /* Search the whole DAG rooted at the given object. */
-           fake.next = NULL;
-           fake.obj = (Obj_Entry *)obj;
-           fake.name = 0;
-           res = symlook_needed(&req, &fake, &donelist);
+           res = symlook_list(&req, &obj->dagmembers, &donelist);
            if (res == 0) {
                def = req.sym_out;
                defobj = req.defobj_out;
@@ -2945,21 +2952,25 @@ r_debug_state(struct r_debug* rd, struct link_map *m)
 
 /*
  * Get address of the pointer variable in the main program.
+ * Prefer non-weak symbol over the weak one.
  */
 static const void **
-get_program_var_addr(const char *name)
+get_program_var_addr(const char *name, RtldLockState *lockstate)
 {
-    const Obj_Entry *obj;
     SymLook req;
+    DoneList donelist;
 
     symlook_init(&req, name);
-    for (obj = obj_main;  obj != NULL;  obj = obj->next) {
-       if (symlook_obj(&req, obj) == 0) {
+    req.lockstate = lockstate;
+    donelist_init(&donelist);
+    if (symlook_global(&req, &donelist) != 0)
+       return (NULL);
+    if (ELF_ST_TYPE(req.sym_out->st_info) == STT_FUNC)
+       return ((const void **)make_function_pointer(req.sym_out,
+         req.defobj_out));
+    else
            return ((const void **)(req.defobj_out->relocbase +
              req.sym_out->st_value));
-       }
-    }
-    return (NULL);
 }
 
 /*
@@ -2972,13 +2983,55 @@ set_program_var(const char *name, const void *value)
 {
     const void **addr;
 
-    if ((addr = get_program_var_addr(name)) != NULL) {
+    if ((addr = get_program_var_addr(name, NULL)) != NULL) {
        dbg("\"%s\": *%p <-- %p", name, addr, value);
        *addr = value;
     }
 }
 
 /*
+ * Search the global objects, including dependencies and main object,
+ * for the given symbol.
+ */
+static int
+symlook_global(SymLook *req, DoneList *donelist)
+{
+    SymLook req1;
+    const Objlist_Entry *elm;
+    int res;
+
+    symlook_init_from_req(&req1, req);
+
+    /* Search all objects loaded at program start up. */
+    if (req->defobj_out == NULL ||
+      ELF_ST_BIND(req->sym_out->st_info) == STB_WEAK) {
+       res = symlook_list(&req1, &list_main, donelist);
+       if (res == 0 && (req->defobj_out == NULL ||
+         ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) {
+           req->sym_out = req1.sym_out;
+           req->defobj_out = req1.defobj_out;
+           assert(req->defobj_out != NULL);
+       }
+    }
+
+    /* Search all DAGs whose roots are RTLD_GLOBAL objects. */
+    STAILQ_FOREACH(elm, &list_global, link) {
+       if (req->defobj_out != NULL &&
+         ELF_ST_BIND(req->sym_out->st_info) != STB_WEAK)
+           break;
+       res = symlook_list(&req1, &elm->obj->dagmembers, donelist);
+       if (res == 0 && (req->defobj_out == NULL ||
+         ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) {
+           req->sym_out = req1.sym_out;
+           req->defobj_out = req1.defobj_out;
+           assert(req->defobj_out != NULL);
+       }
+    }
+
+    return (req->sym_out != NULL ? 0 : ESRCH);
+}
+
+/*
  * This is a special version of getenv which is far more efficient
  * at finding LD_ environment vars.
  */
@@ -3018,13 +3071,10 @@ static int
 symlook_default(SymLook *req, const Obj_Entry *refobj)
 {
     DoneList donelist;
-    const Elf_Sym *def;
-    const Obj_Entry *defobj;
     const Objlist_Entry *elm;
     SymLook req1;
     int res;
-    def = NULL;
-    defobj = NULL;
+
     donelist_init(&donelist);
     symlook_init_from_req(&req1, req);
 
@@ -3032,46 +3082,25 @@ symlook_default(SymLook *req, const Obj_Entry *refobj)
     if (refobj->symbolic && !donelist_check(&donelist, refobj)) {
        res = symlook_obj(&req1, refobj);
        if (res == 0) {
-           def = req1.sym_out;
-           defobj = req1.defobj_out;
-           assert(defobj != NULL);
+           req->sym_out = req1.sym_out;
+           req->defobj_out = req1.defobj_out;
+           assert(req->defobj_out != NULL);
        }
     }
 
-    /* Search all objects loaded at program start up. */
-    if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) {
-       res = symlook_list(&req1, &list_main, &donelist);
-       if (res == 0 &&
-         (def == NULL || ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) {
-           def = req1.sym_out;
-           defobj = req1.defobj_out;
-           assert(defobj != NULL);
-       }
-    }
-
-    /* Search all DAGs whose roots are RTLD_GLOBAL objects. */
-    STAILQ_FOREACH(elm, &list_global, link) {
-       if (def != NULL && ELF_ST_BIND(def->st_info) != STB_WEAK)
-           break;
-       res = symlook_list(&req1, &elm->obj->dagmembers, &donelist);
-       if (res == 0 &&
-         (def == NULL || ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) {
-           def = req1.sym_out;
-           defobj = req1.defobj_out;
-           assert(defobj != NULL);
-       }
-    }
+    symlook_global(req, &donelist);
 
     /* Search all dlopened DAGs containing the referencing object. */
     STAILQ_FOREACH(elm, &refobj->dldags, link) {
-       if (def != NULL && ELF_ST_BIND(def->st_info) != STB_WEAK)
+       if (req->sym_out != NULL &&
+         ELF_ST_BIND(req->sym_out->st_info) != STB_WEAK)
            break;
        res = symlook_list(&req1, &elm->obj->dagmembers, &donelist);
-       if (res == 0 &&
-         (def == NULL || ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) {
-           def = req1.sym_out;
-           defobj = req1.defobj_out;
-           assert(defobj != NULL);
+       if (res == 0 && (req->sym_out == NULL ||
+         ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) {
+           req->sym_out = req1.sym_out;
+           req->defobj_out = req1.defobj_out;
+           assert(req->defobj_out != NULL);
        }
     }
 
@@ -3081,22 +3110,17 @@ symlook_default(SymLook *req, const Obj_Entry *refobj)
      * dynamic linker services such as dlopen.  Only the values listed
      * in the "exports" array can be resolved from the dynamic linker.
      */
-    if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) {
+    if (req->sym_out == NULL ||
+      ELF_ST_BIND(req->sym_out->st_info) == STB_WEAK) {
        res = symlook_obj(&req1, &obj_rtld);
        if (res == 0 && is_exported(req1.sym_out)) {
-           def = req1.sym_out;
-           defobj = req1.defobj_out;
-           assert(defobj != NULL);
+           req->sym_out = req1.sym_out;
+           req->defobj_out = req1.defobj_out;
+           assert(req->defobj_out != NULL);
        }
     }
 
-    if (def != NULL) {
-       assert(defobj != NULL);
-       req->defobj_out = defobj;
-       req->sym_out = def;
-       return (0);
-    }
-    return (ESRCH);
+    return (req->sym_out != NULL ? 0 : ESRCH);
 }
 
 static int
@@ -3132,53 +3156,33 @@ symlook_list(SymLook *req, const Objlist *objlist, DoneList *dlp)
 }
 
 /*
- * Search the symbol table of a shared object and all objects needed
- * by it for a symbol of the given name.  Search order is
- * breadth-first.  Returns a pointer to the symbol, or NULL if no
- * definition was found.
+ * Search the chain of DAGS cointed to by the given Needed_Entry
+ * for a symbol of the given name.  Each DAG is scanned completely
+ * before advancing to the next one.  Returns a pointer to the symbol,
+ * or NULL if no definition was found.
  */
 static int
 symlook_needed(SymLook *req, const Needed_Entry *needed, DoneList *dlp)
 {
-    const Elf_Sym *def, *def_w;
+    const Elf_Sym *def;
     const Needed_Entry *n;
-    const Obj_Entry *defobj, *defobj1;
+    const Obj_Entry *defobj;
     SymLook req1;
     int res;
 
-    def = def_w = NULL;
+    def = NULL;
     defobj = NULL;
     symlook_init_from_req(&req1, req);
     for (n = needed; n != NULL; n = n->next) {
-       if (n->obj == NULL || donelist_check(dlp, n->obj) ||
-           (res = symlook_obj(&req1, n->obj)) != 0)
+       if (n->obj == NULL ||
+           (res = symlook_list(&req1, &n->obj->dagmembers, dlp)) != 0)
            continue;
+       if (def == NULL || ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK) {
        def = req1.sym_out;
        defobj = req1.defobj_out;
-       if (ELF_ST_BIND(def->st_info) != STB_WEAK) {
-           req->defobj_out = defobj;
-           req->sym_out = def;
-           return (0);
-       }
-    }
-    /*
-     * There we come when either symbol definition is not found in
-     * directly needed objects, or found symbol is weak.
-     */
-    for (n = needed; n != NULL; n = n->next) {
-       if (n->obj == NULL)
-           continue;
-       res = symlook_needed(&req1, n->obj->needed, dlp);
-       if (res != 0)
-           continue;
-       def_w = req1.sym_out;
-       defobj1 = req1.defobj_out;
-       if (def == NULL || ELF_ST_BIND(def_w->st_info) != STB_WEAK) {
-           def = def_w;
-           defobj = defobj1;
+           if (ELF_ST_BIND(def->st_info) != STB_WEAK)
+               break;
        }
-       if (ELF_ST_BIND(def_w->st_info) != STB_WEAK)
-           break;
     }
     if (def != NULL) {
        req->sym_out = def;