#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