[GHC] #8935: Obscure linker bug leads to crash in GHCi

GHC ghc-devs at haskell.org
Tue May 6 23:10:35 UTC 2014


#8935: Obscure linker bug leads to crash in GHCi
-------------------------------------+------------------------------------
        Reporter:  simonmar          |            Owner:  simonmar
            Type:  bug               |           Status:  patch
        Priority:  high              |        Milestone:  7.8.3
       Component:  Runtime System    |          Version:  7.8.1-rc2
      Resolution:                    |         Keywords:
Operating System:  Unknown/Multiple  |     Architecture:  Unknown/Multiple
 Type of failure:  GHCi crash        |       Difficulty:  Rocket Science
       Test Case:                    |       Blocked By:
        Blocking:                    |  Related Tickets:
-------------------------------------+------------------------------------

Comment (by dagit):

 Here is the test program I use:
 {{{
 #include <dlfcn.h>
 #include <stdio.h>
 #include <stdlib.h>

 int jason;
 extern char**environ;

 char envname[]  = "environ";
 char jasname[]  = "jason";
 char weakname[] = "weak_jason";

 void dlsym_check(void *handle, char * lib, char * sym){
     void * e = dlsym(handle,sym);
     fprintf(stderr, "dlsym(\"%s\", \"%s\") = %p\n", lib, sym, e);
     char * error = dlerror();
     if( error != NULL )
     fprintf(stderr, "Errors: %s\n", error);
 }

 int main(int argc, char *argv[])
 {
   if( argc < 3 ){
     printf("usage: ./check-environ <path to shared object> <path to shared
 object>\n");
     exit(1);
   }
   char* so  = argv[1];
   char* so2 = argv[2];
   void *deflt, *hdl, *hdl2;

   deflt = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL);
   dlsym_check(deflt, so, envname);
   hdl = dlopen(so, RTLD_LAZY | RTLD_GLOBAL);
   if( hdl == NULL ){
     fprintf(stderr, "Error: %s", dlerror());
     exit(1);
   }
   hdl2 = dlopen(so2, RTLD_LAZY | RTLD_GLOBAL);
   if( hdl2 == NULL ){
     fprintf(stderr, "Error: %s", dlerror());
     exit(1);
   }

   fprintf(stderr, "\nFind a symbol that is located here and libc\n");
   dlsym_check(deflt,        so, envname);
   dlsym_check(hdl,          so, envname);
   dlsym_check(RTLD_DEFAULT, so, envname);
   dlsym_check(RTLD_NEXT,    so, envname);

   fprintf(stderr, "\nFind a symbol that is only defined in this
 executable\n");
   dlsym_check(deflt,        so, jasname);
   dlsym_check(hdl,          so, jasname);
   dlsym_check(RTLD_DEFAULT, so, jasname);
   dlsym_check(RTLD_NEXT,    so, jasname);

   fprintf(stderr, "\nFind a symbol that is only defined weak in a shared
 object\n");
   dlsym_check(deflt,        so2, weakname);
   dlsym_check(hdl2,         so2, weakname);
   dlsym_check(RTLD_DEFAULT, so2, weakname);
   dlsym_check(RTLD_NEXT,    so2, weakname);
 }
 }}}

 That lives in check-environ.c, so I build that with:
 {{{
 LDFLAGS=-ldl CFLAGS=-rdynamic make check-environ
 }}}

 (No actual build file required, I just use implicit rules.)

 I also have test.c, containing just one line:
 {{{
 int __attribute__((weak)) weak_jason;
 }}}

 Built with: `gcc -shared -fpic test.c -o test.so`

 Notice I've added a symbol "jason" that is not defined anywhere but in the
 executable. I use `-rdynamic` as the manpage for `dlsym` suggests. The
 test.so shared object has a weak symbol definition for `weak_jason` (you
 should double check me). I've also duplicated the handle options to
 `dlsym` just so we can see how they change the behavior.

 When I run it:
 {{{
 $  ./check-environ libgmp.so ./test.so 2>&1
 dlsym("libgmp.so", "environ") = 0x31e45bd508

 Find a symbol that is located here and libc
 dlsym("libgmp.so", "environ") = 0x31e45bd508
 dlsym("libgmp.so", "environ") = 0x31e45bd508
 dlsym("libgmp.so", "environ") = 0x31e45bd508
 dlsym("libgmp.so", "environ") = 0x31e45bd508

 Find a symbol that is only defined in this executable
 dlsym("libgmp.so", "jason") = 0x60208c
 dlsym("libgmp.so", "jason") = (nil)
 Errors: /lib64/libgmp.so: undefined symbol: jason
 dlsym("libgmp.so", "jason") = 0x60208c
 dlsym("libgmp.so", "jason") = (nil)

 Find a symbol that is only defined weak in a shared object
 dlsym("./test.so", "weak_jason") = 0x7f8de2f1e02c
 dlsym("./test.so", "weak_jason") = 0x7f8de2f1e02c
 dlsym("./test.so", "weak_jason") = 0x7f8de2f1e02c
 dlsym("./test.so", "weak_jason") = 0x7f8de2f1e02c
 }}}

 I then ran it with `LD_DEBUG=all`, and looked at the output for `environ`.
 {{{
  Find a symbol that is located here and libc
      21283:     symbol=environ;  lookup in file=./check-environ [0]
      21283:     symbol=environ;  lookup in file=/lib64/libdl.so.2 [0]
      21283:     symbol=environ;  lookup in file=/lib64/libc.so.6 [0]
      21283:     binding file ./check-environ [0] to /lib64/libc.so.6 [0]:
 normal symbol `environ'
 dlsym("libgmp.so", "environ") = 0x31e45bd508
      21283:     symbol=environ;  lookup in file=/lib64/libgmp.so [0]
      21283:     symbol=environ;  lookup in file=/lib64/libc.so.6 [0]
      21283:     binding file /lib64/libgmp.so [0] to /lib64/libc.so.6 [0]:
 normal symbol `environ'
 dlsym("libgmp.so", "environ") = 0x31e45bd508
      21283:     symbol=environ;  lookup in file=./check-environ [0]
      21283:     symbol=environ;  lookup in file=/lib64/libdl.so.2 [0]
      21283:     symbol=environ;  lookup in file=/lib64/libc.so.6 [0]
      21283:     binding file ./check-environ [0] to /lib64/libc.so.6 [0]:
 normal symbol `environ'
 dlsym("libgmp.so", "environ") = 0x31e45bd508
      21283:     symbol=environ;  lookup in file=/lib64/libdl.so.2 [0]
      21283:     symbol=environ;  lookup in file=/lib64/libc.so.6 [0]
      21283:     binding file ./check-environ [0] to /lib64/libc.so.6 [0]:
 normal symbol `environ'
 dlsym("libgmp.so", "environ") = 0x31e45bd508
 }}}

 In each cases, once it found the definition in libc it stopped looking
 (even though it's `WEAK`). The search for `weak_jason` is similar, but it
 has to keep looking until it gets to test.so. I think it's safe to say
 that the behavior is not specific to environ or libc:
 {{{
 Find a symbol that is only defined weak in a shared object
      21283:     symbol=weak_jason;  lookup in file=./check-environ [0]
      21283:     symbol=weak_jason;  lookup in file=/lib64/libdl.so.2 [0]
      21283:     symbol=weak_jason;  lookup in file=/lib64/libc.so.6 [0]
      21283:     symbol=weak_jason;  lookup in file=/lib64/ld-
 linux-x86-64.so.2 [0]
      21283:     symbol=weak_jason;  lookup in file=/lib64/libgmp.so [0]
      21283:     symbol=weak_jason;  lookup in file=./test.so [0]
      21283:     binding file ./check-environ [0] to ./test.so [0]: normal
 symbol `weak_jason'
 dlsym("./test.so", "weak_jason") = 0x7f0e99fbd02c
      21283:     symbol=weak_jason;  lookup in file=./test.so [0]
      21283:     binding file ./test.so [0] to ./test.so [0]: normal symbol
 `weak_jason'
 dlsym("./test.so", "weak_jason") = 0x7f0e99fbd02c
      21283:     symbol=weak_jason;  lookup in file=./check-environ [0]
      21283:     symbol=weak_jason;  lookup in file=/lib64/libdl.so.2 [0]
      21283:     symbol=weak_jason;  lookup in file=/lib64/libc.so.6 [0]
      21283:     symbol=weak_jason;  lookup in file=/lib64/ld-
 linux-x86-64.so.2 [0]
      21283:     symbol=weak_jason;  lookup in file=/lib64/libgmp.so [0]
      21283:     symbol=weak_jason;  lookup in file=./test.so [0]
      21283:     binding file ./check-environ [0] to ./test.so [0]: normal
 symbol `weak_jason'
 dlsym("./test.so", "weak_jason") = 0x7f0e99fbd02c
      21283:     symbol=weak_jason;  lookup in file=/lib64/libdl.so.2 [0]
      21283:     symbol=weak_jason;  lookup in file=/lib64/libc.so.6 [0]
      21283:     symbol=weak_jason;  lookup in file=/lib64/ld-
 linux-x86-64.so.2 [0]
      21283:     symbol=weak_jason;  lookup in file=/lib64/libgmp.so [0]
      21283:     symbol=weak_jason;  lookup in file=./test.so [0]
      21283:     binding file ./check-environ [0] to ./test.so [0]: normal
 symbol `weak_jason'
 dlsym("./test.so", "weak_jason") = 0x7f0e99fbd02c
 }}}

 In the case of the `jason` symbol:
 {{{
 Find a symbol that is only defined in this executable
      21283:     symbol=jason;  lookup in file=./check-environ [0]
      21283:     binding file ./check-environ [0] to ./check-environ [0]:
 normal symbol `jason'
 dlsym("libgmp.so", "jason") = 0x60208c
      21283:     symbol=jason;  lookup in file=/lib64/libgmp.so [0]
      21283:     symbol=jason;  lookup in file=/lib64/libc.so.6 [0]
      21283:     symbol=jason;  lookup in file=/lib64/ld-linux-x86-64.so.2
 [0]
      21283:     /lib64/libgmp.so: error: symbol lookup error: undefined
 symbol: jason (fatal)
 dlsym("libgmp.so", "jason") = (nil)
      21283:     symbol=__dcgettext;  lookup in file=./check-environ [0]
      21283:     symbol=__dcgettext;  lookup in file=/lib64/libdl.so.2 [0]
      21283:     symbol=__dcgettext;  lookup in file=/lib64/libc.so.6 [0]
      21283:     binding file /lib64/libdl.so.2 [0] to /lib64/libc.so.6
 [0]: normal symbol `__dcgettext' [GLIBC_2.2.5]
      21283:     symbol=__asprintf;  lookup in file=./check-environ [0]
      21283:     symbol=__asprintf;  lookup in file=/lib64/libdl.so.2 [0]
      21283:     symbol=__asprintf;  lookup in file=/lib64/libc.so.6 [0]
      21283:     binding file /lib64/libdl.so.2 [0] to /lib64/libc.so.6
 [0]: normal symbol `__asprintf' [GLIBC_2.2.5]
      21283:     symbol=free;  lookup in file=./check-environ [0]
      21283:     symbol=free;  lookup in file=/lib64/libdl.so.2 [0]
      21283:     symbol=free;  lookup in file=/lib64/libc.so.6 [0]
      21283:     binding file /lib64/libdl.so.2 [0] to /lib64/libc.so.6
 [0]: normal symbol `free' [GLIBC_2.2.5]
 Errors: /lib64/libgmp.so: undefined symbol: jason
      21283:     symbol=jason;  lookup in file=./check-environ [0]
      21283:     binding file ./check-environ [0] to ./check-environ [0]:
 normal symbol `jason'
 dlsym("libgmp.so", "jason") = 0x60208c
      21283:     symbol=jason;  lookup in file=/lib64/libdl.so.2 [0]
      21283:     symbol=jason;  lookup in file=/lib64/libc.so.6 [0]
      21283:     symbol=jason;  lookup in file=/lib64/ld-linux-x86-64.so.2
 [0]
      21283:     symbol=jason;  lookup in file=/lib64/libgmp.so [0]
      21283:     symbol=jason;  lookup in file=./test.so [0]
 dlsym("libgmp.so", "jason") = (nil)
 }}}

 The point though, is that `dlsym` is doing exactly what it should: It
 returns the first global definition it finds.

--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/8935#comment:36>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list