Runtime loader does not use all runpaths if first runpath has a problem
Bruno Haible and I are working on a GetText problem on Alpine Linux. The problem surfaces when the latest versions of libiconv and gettext are used and placed in a non-standard location. In this case, runpaths are used so the proper libraries are found at runtime.
When building libiconv and gettext I use flags that include two runpaths. One of them is an $ORIGIN
-based runpath, but the origin is crafted so it passes through a makefile without problems by escaping the dollar sign: $$ORIGIN
.
$ gcc -o conftest -g2 -O2 -march=native -fPIC -pthread -I/home/jwalton/tmp/ok2delete/include
-DNDEBUG -L/home/jwalton/tmp/ok2delete/lib -Wl,-R,'$$ORIGIN/../lib'
-Wl,-R,/home/jwalton/tmp/ok2delete/lib -Wl,--enable-new-dtags iconv-test.c -liconv -ldl -lpthread
Autoconf fails its conftest, which can be duplicated:
$ ./conftest
Error loading shared library libiconv.so.2: No such file or directory (needed by ./conftest)
Error relocating ./conftest: libiconv_open: symbol not found
Error relocating ./conftest: libiconv_close: symbol not found
Error relocating ./conftest: libiconv: symbol not found
And:
$ ldd conftest
/lib/ld-musl-x86_64.so.1 (0x7f34ef8f5000)
Error loading shared library libiconv.so.2: No such file or directory (needed by conftest)
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f34ef8f5000)
Error relocating conftest: libiconv_open: symbol not found
Error relocating conftest: libiconv_close: symbol not found
Error relocating conftest: libiconv: symbol not found
Examining the runpath in the executable (notice the two runpaths):
$ objdump -x conftest | grep RUNPATH
RUNPATH $$ORIGIN/../lib:/home/jwalton/tmp/ok2delete/lib
$$ORIGIN/../lib
is malformed because it was not escaped by the makefile. When I modify the compile command to use $ORIGIN
(rather then $$ORIGIN
), then things work as expected:
$ gcc -o conftest -g2 -O2 -march=native -fPIC -pthread -I/home/jwalton/tmp/ok2delete/include
-DNDEBUG -L/home/jwalton/tmp/ok2delete/lib -Wl,-R,'$ORIGIN/../lib'
-Wl,-R,/home/jwalton/tmp/ok2delete/lib -Wl,--enable-new-dtags iconv-test.c -liconv -ldl -lpthread
$ ./conftest
$
And (notice the two runpaths):
$ objdump -x conftest | grep RUNPATH
RUNPATH $ORIGIN/../lib:/home/jwalton/tmp/ok2delete/lib
And finally:
$ ldd conftest
/lib/ld-musl-x86_64.so.1 (0x7fabc3419000)
libiconv.so.2 => /home/jwalton/tmp/ok2delete/lib/libiconv.so.2 (0x7fabc332a000)
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7fabc3419000)
So it looks like the loader stops processing runpaths if it encounters a bad runpath. In this case, the loader did not process the runpath /home/jwalton/tmp/ok2delete/lib
because of the malformed runpath $$ORIGIN/../lib
.
I can't provide a origin-based runpath that works with both configure tests and compile through makefiles. Because I have to choose, I select the one that works with makefiles because make && make check
are the important goals.
I'm fairly certain this is unexpected behavior from the loader (though I can't find the behavior documented).
Here is the test program Autoconf creates to test for availability of libiconv.
$ cat iconv-test.c
#include <iconv.h>
#include <string.h>
#ifndef ICONV_CONST
# define ICONV_CONST
#endif
int
main ()
{
int result = 0;
/* Test against AIX 5.1 bug: Failures are not distinguishable from successful
returns. */
{
iconv_t cd_utf8_to_88591 = iconv_open ("ISO8859-1", "UTF-8");
if (cd_utf8_to_88591 != (iconv_t)(-1))
{
static ICONV_CONST char input[] = "\342\202\254"; /* EURO SIGN */
char buf[10];
ICONV_CONST char *inptr = input;
size_t inbytesleft = strlen (input);
char *outptr = buf;
size_t outbytesleft = sizeof (buf);
size_t res = iconv (cd_utf8_to_88591,
&inptr, &inbytesleft,
&outptr, &outbytesleft);
if (res == 0)
result |= 1;
iconv_close (cd_utf8_to_88591);
}
}
/* Test against Solaris 10 bug: Failures are not distinguishable from
successful returns. */
{
iconv_t cd_ascii_to_88591 = iconv_open ("ISO8859-1", "646");
if (cd_ascii_to_88591 != (iconv_t)(-1))
{
static ICONV_CONST char input[] = "\263";
char buf[10];
ICONV_CONST char *inptr = input;
size_t inbytesleft = strlen (input);
char *outptr = buf;
size_t outbytesleft = sizeof (buf);
size_t res = iconv (cd_ascii_to_88591,
&inptr, &inbytesleft,
&outptr, &outbytesleft);
if (res == 0)
result |= 2;
iconv_close (cd_ascii_to_88591);
}
}
/* Test against AIX 6.1..7.1 bug: Buffer overrun. */
{
iconv_t cd_88591_to_utf8 = iconv_open ("UTF-8", "ISO-8859-1");
if (cd_88591_to_utf8 != (iconv_t)(-1))
{
static ICONV_CONST char input[] = "\304";
static char buf[2] = { (char)0xDE, (char)0xAD };
ICONV_CONST char *inptr = input;
size_t inbytesleft = 1;
char *outptr = buf;
size_t outbytesleft = 1;
size_t res = iconv (cd_88591_to_utf8,
&inptr, &inbytesleft,
&outptr, &outbytesleft);
if (res != (size_t)(-1) |outptr - buf > 1 |buf[1] != (char)0xAD)
result |= 4;
iconv_close (cd_88591_to_utf8);
}
}
#if 0 /* This bug could be worked around by the caller. */
/* Test against HP-UX 11.11 bug: Positive return value instead of 0. */
{
iconv_t cd_88591_to_utf8 = iconv_open ("utf8", "iso88591");
if (cd_88591_to_utf8 != (iconv_t)(-1))
{
static ICONV_CONST char input[] = "\304rger mit b\366sen B\374bchen ohne Augenma\337";
char buf[50];
ICONV_CONST char *inptr = input;
size_t inbytesleft = strlen (input);
char *outptr = buf;
size_t outbytesleft = sizeof (buf);
size_t res = iconv (cd_88591_to_utf8,
&inptr, &inbytesleft,
&outptr, &outbytesleft);
if ((int)res > 0)
result |= 8;
iconv_close (cd_88591_to_utf8);
}
}
#endif
/* Test against HP-UX 11.11 bug: No converter from EUC-JP to UTF-8 is
provided. */
{
/* Try standardized names. */
iconv_t cd1 = iconv_open ("UTF-8", "EUC-JP");
/* Try IRIX, OSF/1 names. */
iconv_t cd2 = iconv_open ("UTF-8", "eucJP");
/* Try AIX names. */
iconv_t cd3 = iconv_open ("UTF-8", "IBM-eucJP");
/* Try HP-UX names. */
iconv_t cd4 = iconv_open ("utf8", "eucJP");
if (cd1 == (iconv_t)(-1) && cd2 == (iconv_t)(-1)
&& cd3 == (iconv_t)(-1) && cd4 == (iconv_t)(-1))
result |= 16;
if (cd1 != (iconv_t)(-1))
iconv_close (cd1);
if (cd2 != (iconv_t)(-1))
iconv_close (cd2);
if (cd3 != (iconv_t)(-1))
iconv_close (cd3);
if (cd4 != (iconv_t)(-1))
iconv_close (cd4);
}
return result;
Here's an strace
of the unexpected behavior using $$ORIGIN/../lib
in case it helps.
$ gcc -o conftest -g2 -O2 -march=native -fPIC -pthrea
d -I/home/jwalton/tmp/ok2delete/include -DNDEBUG -L/home/jwalton/tmp/ok2delete/l
ib -Wl,-R,'$$ORIGIN/../lib' -Wl,-R,/home/jwalton/tmp/ok2delete/lib -Wl,--enable-
new-dtags iconv-test.c -liconv -ldl -lpthread
$ strace ./conftest
execve("./conftest", ["./conftest"], 0x7fff34deea30 /* 17 vars */) = 0
arch_prctl(ARCH_SET_FS, 0x7f0007b47d48) = 0
set_tid_address(0x7f0007b4831c) = 10517
open("/etc/ld-musl-x86_64.path", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib/libiconv.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/usr/local/lib/libiconv.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/usr/lib/libiconv.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
writev(2, [{iov_base="Error loading shared library lib"..., iov_len=69}, {iov_base=" (needed by ", iov_len=12}], 2Error loading shared library libiconv.so.2: No such file or directory (needed by ) = 81
writev(2, [{iov_base="./conftest)", iov_len=11}, {iov_base=NULL, iov_len=0}], 2./conftest)) = 11
writev(2, [{iov_base="\n", iov_len=1}, {iov_base=NULL, iov_len=0}], 2
) = 1
mprotect(0x7f0007b44000, 4096, PROT_READ) = 0
writev(2, [{iov_base="Error relocating ./conftest: lib"..., iov_len=60}, {iov_base=NULL, iov_len=0}], 2Error relocating ./conftest: libiconv_open: symbol not found) = 60
writev(2, [{iov_base="\n", iov_len=1}, {iov_base=NULL, iov_len=0}], 2
) = 1
writev(2, [{iov_base="Error relocating ./conftest: lib"..., iov_len=61}, {iov_base=NULL, iov_len=0}], 2Error relocating ./conftest: libiconv_close: symbol not found) = 61
writev(2, [{iov_base="\n", iov_len=1}, {iov_base=NULL, iov_len=0}], 2
) = 1
writev(2, [{iov_base="Error relocating ./conftest: lib"..., iov_len=55}, {iov_base=NULL, iov_len=0}], 2Error relocating ./conftest: libiconv: symbol not found) = 55
writev(2, [{iov_base="\n", iov_len=1}, {iov_base=NULL, iov_len=0}], 2
) = 1
mprotect(0x563073e08000, 4096, PROT_READ) = 0
exit_group(127) = ?
+++ exited with 127 +++