Bug 25059

Summary: dlopen(..,RTLD_GLOBAL) doesn't work for shared libraries linked to the loaded one
Product: Base System Reporter: nikip <nikip>
Component: binAssignee: Dag-Erling Smørgrav <des>
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: 4.2-STABLE   
Hardware: Any   
OS: Any   
Attachments:
Description Flags
rtld.diff none

Description nikip 2001-02-13 09:20:00 UTC
If a shared library is loaded with dlopen(..., RTLD_GLOBAL) and it is linked at compile time to another *shared* library then the symbols of the second library are not exported.

How-To-Repeat: The test suite goes as follows.
There is a shared library "dl1.so" which is linked to another shared library "st1.so".
The second library exports a constant, say 'char *ok'.
Then there is a third shared library, called "dl2.so" which is independent of the other two but uses the symbol defined in "st1.so".
The main program dlopen()'s "dl1.so" and "dl2.so" and calls a function in "dl2.so" which uses the constant 'ok' and you get the error that it is not defined.

You can download the test suite from http://www.cs.kuleuven.ac.be/~pelov/pam/download/dltest2.tar.gz
To run it just type 'gmake test'.
The test runs ok on both Linux and Solaris machines.
A similar problem appeared in some of the early development versions of glibc2.

----------- begin Makefile -----------
CFLAGS  = -g -Wall
LDFLAGS = -export-dynamic -fpic

all: main st1.so dl1.so dl2.so

%.so: %.o
	$(LD) $(LDFLAGS) -o $@ -shared $<

dl1.so: dl1.o st1.so
	$(LD) $(LDFLAGS) -o $@ -shared $^

clean:
	rm -f main *.so *.o

test: all
	LD_LIBRARY_PATH=. && export LD_LIBRARY_PATH && ./main

----------- end Makefile ---------------

----------- begin main.c  ---------------
#include <stdio.h>
#include <dlfcn.h>

void * open_lib(const char *name)
{
  void *lib = dlopen(name, RTLD_NOW | RTLD_GLOBAL);
  if (lib == NULL) {
    printf("dlopen error: %s\n", dlerror());
    exit(1);
  }
  return lib;
}

int main() {
   void *dl1_lib = open_lib("dl1.so");
   void *dl2_lib = open_lib("dl2.so");
   void (*func)(void) = dlsym(dl2_lib, "test_dl2");

   if (func == 0) {
      printf("dlsym error: %s\n", dlerror());
      return 1;
   }
   func();
   
   dlclose(dl1_lib);
   dlclose(dl2_lib);
   return 0;
}
----------- end main.c  ---------------

----------- begin dl1.c  --------------
void test_dl1() {
  /* some dummy function */
}
----------- end dl1.c  ----------------

----------- begin st1.c  --------------
char *ok = "\n The dlopen() function is OK\n";
----------- end st1.c  ----------------

----------- begin dl2.c  --------------
#include <stdio.h>

extern char *ok;

void test_dl2() {
  printf("%s\n", ok);
}
----------- end dl2.c  ----------------

----------- begin output --------------
bash-2.03$ gmake test
cc -g -Wall  -export-dynamic -fpic  main.c   -o main
cc -g -Wall   -c -o st1.o st1.c
ld -export-dynamic -fpic -o st1.so -shared st1.o
cc -g -Wall   -c -o dl1.o dl1.c
ld -export-dynamic -fpic -o dl1.so -shared dl1.o st1.so
cc -g -Wall   -c -o dl2.o dl2.c
ld -export-dynamic -fpic -o dl2.so -shared dl2.o
LD_LIBRARY_PATH=. && export LD_LIBRARY_PATH && ./main
dlopen error: ./dl2.so: Undefined symbol "ok"
gmake: *** [test] Error 1
rm st1.o dl2.o
Comment 1 des 2002-02-23 23:21:49 UTC
     RTLD_GLOBAL   Symbols from this shared object and its directed acyclic
                   graph (DAG) of needed objects will be available for resolv
                   ing undefined references from all other shared objects.

but this is a lie - only the object itself is searched, not its DAG.

The attached patch fixes this by adding a flag to the Obj_Entry
structure that marks an object as global, and changing symlook_list()
to search each global object's DAG if the object itself doesn't have
the requested symbol.  With this patch applied, the test program
referenced in the PR works.

(some might argue that symlook_obj() should be changed instead - but
changing symlook_list() was simpler, and the only difference it makes
is, in the worst case, a slightly longer search)

DES
-- 
Dag-Erling Smorgrav - des@ofug.org
Comment 2 Dag-Erling Smørgrav freebsd_committer freebsd_triage 2002-02-23 23:36:53 UTC
Responsible Changed
From-To: freebsd-bugs->des

I have a patch for this.
Comment 3 John Polstra 2002-02-27 01:11:26 UTC
I'm a little bit wary of Dag-Erling's patch because it changes the
semantics of symlook_list(), which is supposed to be a utility
function that does a particular thing.  I would prefer to change
symlook_default() to search the RTLD_GLOBAL DAGs explicitly, as in the
patch below.  This patch makes Nikolay Pelov's test case work OK.

Dag-Erling, if this patch looks OK to you and it solves your problem,
please feel free to commit it for me.  I am leaving on holiday, and
won't be able to do it myself for a couple weeks.

John

Index: rtld.c
===================================================================
RCS file: /home/ncvs/src/libexec/rtld-elf/rtld.c,v
retrieving revision 1.59
diff -U9 -r1.59 rtld.c
--- rtld.c      4 Feb 2002 10:44:15 -0000       1.59
+++ rtld.c      26 Feb 2002 18:49:47 -0000
@@ -1909,21 +1909,24 @@
        symp = symlook_list(name, hash, &elm->obj->dagmembers, &obj, in_plt,
          &donelist);
        if (symp != NULL &&
          (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) {
            def = symp;
            defobj = obj;
        }
     }
 
-    /* Search all RTLD_GLOBAL objects. */
-    if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) {
-       symp = symlook_list(name, hash, &list_global, &obj, in_plt, &donelist);
+    /* 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;
+       symp = symlook_list(name, hash, &elm->obj->dagmembers, &obj, in_plt,
+         &donelist);
        if (symp != NULL &&
          (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) {
            def = symp;
            defobj = obj;
        }
     }
 
     /*
      * Search the dynamic linker itself, and possibly resolve the
Comment 4 Dag-Erling Smørgrav freebsd_committer freebsd_triage 2002-03-08 17:34:50 UTC
State Changed
From-To: open->feedback

Fixed in -CURRENT, awaiting MFC.
Comment 5 Dag-Erling Smørgrav freebsd_committer freebsd_triage 2002-04-02 23:59:18 UTC
State Changed
From-To: feedback->closed

MFCed, thanks!