[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