why my way does not work
First, shared libraries on UNIX are designed to mimic the way archive libraries work (archive libraries were there first). In particular that means that if you have libfoo.so
and libbar.so
, both defining symbol foo
, then whichever library is loaded first is the one that wins: all references to foo
from anywhere within the program (including from libbar.so
) will bind to libfoo.so
s definition of foo
.
This mimics what would happen if you linked your program against libfoo.a
and libbar.a
, where both archive libraries defined the same symbol foo
. More info on archive linking here.
It should be clear from above, that if libblas.so.3
and libopenblas.so.0
define the same set of symbols (which they do), and if libblas.so.3
is loaded into the process first, then routines from libopenblas.so.0
will never be called.
Second, you've correctly decided that since R
directly links against libR.so
, and since libR.so
directly links against libblas.so.3
, it is guaranteed that libopenblas.so.0
will lose the battle.
However, you erroneously decided that Rscript
is better, but it's not: Rscript
is a tiny binary (11K on my system; compare to 2.4MB for libR.so
), and approximately all it does is exec
of R
. This is trivial to see in strace
output:
strace -e trace=execve /usr/bin/Rscript --default-packages=base --vanilla /dev/null
execve("/usr/bin/Rscript", ["/usr/bin/Rscript", "--default-packages=base", "--vanilla", "/dev/null"], [/* 42 vars */]) = 0
execve("/usr/lib/R/bin/R", ["/usr/lib/R/bin/R", "--slave", "--no-restore", "--vanilla", "--file=/dev/null", "--args"], [/* 43 vars */]) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=89625, si_status=0, si_utime=0, si_stime=0} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=89626, si_status=0, si_utime=0, si_stime=0} ---
execve("/usr/lib/R/bin/exec/R", ["/usr/lib/R/bin/exec/R", "--slave", "--no-restore", "--vanilla", "--file=/dev/null", "--args"], [/* 51 vars */]) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=89630, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
Which means that by the time your script starts executing, libblas.so.3
has been loaded, and libopenblas.so.0
that will be loaded as a dependency of mmperf.so
will not actually be used for anything.
is it possible at all to make it work
Probably. I can think of two possible solutions:
- Pretend that
libopenblas.so.0
is actually libblas.so.3
- Rebuild entire
R
package against libopenblas.so
.
For #1, you need to ln -s libopenblas.so.0 libblas.so.3
, then make sure that your copy of libblas.so.3
is found before the system one, by setting LD_LIBRARY_PATH
appropriately.
This appears to work for me:
mkdir /tmp/libblas
# pretend that libc.so.6 is really libblas.so.3
cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/libblas/libblas.so.3
LD_LIBRARY_PATH=/tmp/libblas /usr/bin/Rscript /dev/null
Error in dyn.load(file, DLLpath = DLLpath, ...) :
unable to load shared object '/usr/lib/R/library/stats/libs/stats.so':
/usr/lib/liblapack.so.3: undefined symbol: cgemv_
During startup - Warning message:
package ‘stats’ in options("defaultPackages") was not found
Note how I got an error (my "pretend" libblas.so.3
doesn't define symbols expected of it, since it's really a copy of libc.so.6
).
You can also confirm which version of libblas.so.3
is getting loaded this way:
LD_DEBUG=libs LD_LIBRARY_PATH=/tmp/libblas /usr/bin/Rscript /dev/null |& grep 'libblas.so.3'
91533: find library=libblas.so.3 [0]; searching
91533: trying file=/usr/lib/R/lib/libblas.so.3
91533: trying file=/usr/lib/x86_64-linux-gnu/libblas.so.3
91533: trying file=/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/amd64/server/libblas.so.3
91533: trying file=/tmp/libblas/libblas.so.3
91533: calling init: /tmp/libblas/libblas.so.3
For #2, you said:
I have no root access on machines I want to test, so actual linking to OpenBLAS is impossible.
but that seems to be a bogus argument: if you can build libopenblas
, surely you can also build your own version of R
.
Update:
You mentioned in the beginning that libblas.so.3 and libopenblas.so.0 define the same symbol, what does this mean? They have different SONAME, is that insufficient to distinguish them by the system?
The symbols and the SONAME
have nothing to do with each other.
You can see symbols in the output from readelf -Ws libblas.so.3
and readelf -Ws libopenblas.so.0
. Symbols related to BLAS
, such as cgemv_
, will appear in both libraries.
Your confusion about SONAME
possibly comes from Windows. The DLL
s on Windows are designed completely differently. In particular, when FOO.DLL
imports symbol bar
from BAR.DLL
, both the name of the symbol (bar
) and the DLL
from which that symbol was imported (BAR.DLL
) are recorded in the FOO.DLL
s import table.
That makes it easy to have R
import cgemv_
from BLAS.DLL
, while MMPERF.DLL
imports the same symbol from OPENBLAS.DLL
.
However, that makes library interpositioning hard, and works completely differently from the way archive libraries work (even on Windows).
Opinions differ on which design is better overall, but neither system is likely to ever change its model.
There are ways for UNIX to emulate Windows-style symbol binding: see RTLD_DEEPBIND
in dlopen man page. Beware: these are fraught with peril, likely to confuse UNIX experts, are not widely used, and likely to have implementation bugs.
Update 2:
you mean I compile R and install it under my home directory?
Yes.
Then when I want to invoke it, I should explicitly give the path to my version of executable program, otherwise the one on the system might be invoked instead? Or, can I put this path at the first position of environment variable $PATH to cheat the system?
Either way works.