1.はじめに
LinuxOSに付属する既存のコンパイラgcc/g++に加えて別のgcc/g++を導入すると動的リンクのライブラリの問題が発生する事があります。メモとして残します。
環境: CentOS6.7/32bit (64bitではありません)
OS付属のコンパイラ: gcc/g++ version 4.4.7
新規に導入したコンパイラ: gcc/g++ 5.3.0 および 6.1.0
2.事前確認
CentOS6.7/32bitのデフォルトのgcc/g++のバージョンは以下の通り。
$ which gcc
/usr/bin/gcc
$ gcc --version
gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16)
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
3.新しいコンパイラの導入。
コンパイラの導入はいろいろなサイトが取り上げているので、ググればすぐに出てくると思いますが、参考程度に挙げておく。
本家のサイトはこちら。
https://gcc.gnu.org/install/
gccのソースファイルは国内ではこちらにあります。
http://ftp.tsukuba.wide.ad.jp/software/gcc/
上記筑波のサイトより、以下をダウンロード。
gcc-5.3.0.tar.gz
gcc-6.1.0.tar.gz
mpfr-2.4.2.tar.bz2
gmp-4.3.2.tar.bz2
mpc-0.8.1.tar.gz
isl-0.14.tar.bz2 (gcc-5.3用)
isl-0.15.tar.bz2 (gcc-6.1用)
なお、gcc本体以外のmpfr/gmpなどのバージョンは、gccのソースファイルに含まれるファイル「contrib/download_prerequisites」に書いてあるバージョンが推奨だろうと推測して、それにあわせた。
3.1.新しいgccを導入するディレクトリ。
gccのバージョンごとに別々の場所を用意する事にした。
/usr/local/gcc-5.3/
/usr/local/gcc-6.1/
導入作業用のユーザを「inst-user」とする。inst-userは一般ユーザが好ましい。特権ユーザrootだとインストール作業にミスするとOSを破壊する。
まず「inst-user」でログインする。gcc導入用のディレクトリの作成。
$ sudo mkdir /usr/local/gcc-5.3 /usr/local/gcc-6.1
$ sudo chown inst-user /usr/local/gcc-5.3 /usr/local/gcc-6.1
3.2.ソースコードの展開およびmake
作業はユーザinst-userで行う。rootでは行わない。
gcc-5.3.0のコンパイルおよびインストール。
$ gtar xvfz gcc-5.3.0.tar.gz
$ cd gcc-5.3.0
$ tar xjf ../mpfr-2.4.2.tar.bz2
$ ln -sf mpfr-2.4.2 mpfr
$ tar xjf ../gmp-4.3.2.tar.bz2
$ ln -sf gmp-4.3.2 gmp
$ tar xzf ../mpc-0.8.1.tar.gz
$ ln -s mpc-0.8.1 mpc
$ tar xjf ../isl-0.14.tar.bz2
$ ln -sf isl-0.14 isl
$ mkdir ../gccbuild
$ cd ../gccbuild
$ ../gcc-5.3.0/configure --prefix=/usr/local/gcc-5.3 --with-local-prefix=/usr/local/gcc-5.3 --enable-languages=c,c++
$ make
$ make install # <- インストール先のファイルオーナーがinst-userのためroot権限不要
gcc-6.1.0のコンパイルおよびインストール。
$ tar xvfz gcc-6.1.0.tar.gz
$ cd gcc-6.1.0
$ tar xjf ../mpfr-2.4.2.tar.bz2
$ ln -sf mpfr-2.4.2 mpfr
$ tar xjf ../gmp-4.3.2.tar.bz2
$ ln -sf gmp-4.3.2 gmp
$ tar xzf ../mpc-0.8.1.tar.gz
$ ln -s mpc-0.8.1 mpc
$ tar xjf ../isl-0.15.tar.bz2 # <-- gcc-5.3は0.14, gcc-6.1は0.15
$ ln -sf isl-0.15 isl
$ mkdir ../gccbuild2
$ cd ../gccbuild2
$ ../gcc-6.1.0/configure --prefix=/usr/local/gcc-6.1 --with-local-prefix=/usr/local/gcc-6.1 --enable-languages=c,c++
$ make
$ make install # <- インストール先のファイルオーナーがinst-userのためroot権限不要
4.動的リンクライブラリのリンクエラー
ここからが本題です。
上記手順に従って新しいコンパイラを導入すると、g++で動的リンクライブラリのリンクエラーが発生する事がある。
動的リンクライブラリの具体例として以下のソースファイルをコンパイルしてみる。
#include <iostream>
int main(){
std::cout << "HI" << std::endl;
}
サンプルtmp.ccのコンパイル例。
$ g++ --version
g++ (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16)
(出力省略)
$ g++ tmp.cc
$ ls -l a.out
-rwxr-xr-x. 1 inst-user group 6216 6月 3 11:02 2016 a.out
ファイルサイズはたったの6kbyteである。非常に小さいが、これは実行時には動的リンクライブラリがリンクされるため。
動的にリンクされるライブラリはコマンドlddで確認できる。
$ ldd a.out
linux-gate.so.1 => (0x00a8b000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00ccf000)
libm.so.6 => /lib/i686/nosegneg/libm.so.6 (0x00361000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x00c71000)
libc.so.6 => /lib/i686/nosegneg/libc.so.6 (0x0019f000)
/lib/ld-linux.so.2 (0x0017d000)
生成されるa.outはたったの6kbyteであるが、上記のように実行時に動的にファイル「/usr/lib/libstdc++.so.6」、「libm.so.6」、「libc.so.6」などがリンクされる。
では先ほどインストールしたg++バージョン5.3でサンプルtmp.ccをコンパイルしてみよう。
単純な例だと問題なくコンパイルおよび実行できます。
$ /usr/local/gcc-5.3/bin/g++ --version
g++ (GCC) 5.3.0
(出力省略)
$ /usr/local/gcc-5.3/bin/g++ tmp.cc
$ ldd a.out
linux-gate.so.1 => (0x00df4000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00ccf000)
libm.so.6 => /lib/i686/nosegneg/libm.so.6 (0x00361000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x00c71000)
libc.so.6 => /lib/i686/nosegneg/libc.so.6 (0x0019f000)
/lib/ld-linux.so.2 (0x0017d000)
$ ./a.out
HI
だがこれは単純なサンプルだったからに過ぎない。
複雑なプログラム、具体的にはC++11に対応したコードをコンパイルすると上記では正常動作しない。
次のページにC++11のサンプルコードが上がっているが、
上記サイトよりコンテナクラスの初期化」のstd:mapを使う例を引用しファイル「map.cc」に保存してください。
$ /usr/local/gcc-5.3/bin/g++ -std=c++11 map.cc
$ ldd a.out
./a.out: /usr/lib/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./a.out)
linux-gate.so.1 => (0x00782000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00ccf000)
libm.so.6 => /lib/i686/nosegneg/libm.so.6 (0x00361000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x00c71000)
libc.so.6 => /lib/i686/nosegneg/libc.so.6 (0x0019f000)
/lib/ld-linux.so.2 (0x0017d000)
$ ./a.out
./a.out: /usr/lib/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./a.out)
これは何が駄目なのかというと、間違った動的リンクライブラリが動的にリンクされているため。
間違い: /usr/lib/libstdc++.so.6
正解: /usr/local/gcc-5.3/lib/libstdc++.so.6
同じ名前の別のファイルが有ったので間違ってリンクされているというわけです。
先ほどあげたC++のサンプルのページではg++に引数「-Wl,-rpath=/hoge/path」を指定しライブラリの場所を指定することにより対処している。
この現象について以下のページで解説されている
Finding Dynamic or Shared Libraries
解決方法として引数「-Wl,-rpath=/hoge/path」以外に環境変数LD_LIBRARY_PATH、LD_RUN_PATHなどがある。
たいていのサイトでは環境変数「LD_LIBRARY_PATH」を使う方法を挙げてるが、これはセキュリティ上よくない。また誤動作を誘発する可能性がある。
どうよくないかというと、例えばOS標準で入っているfirefoxもlibstdc++.so.6を使っている。
$ ldd /usr/lib/firefox/firefox-bin (抜粋)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00ccf000)
環境変数LD_LIBRARY_PATHを宣言すると、、
$ export LD_LIBRARY_PATH=/usr/local/gcc-5.3/lib
間違ったライブラリをリンクしてしまいます。
$ ldd /usr/lib/firefox/firefox-bin (抜粋)
libstdc++.so.6 => /usr/local/gcc-5.3/lib/libstdc++.so.6 (0x00a0f000)
これはよくない。そのためLD_RUN_PATHのほうが良い。
しかし、こんな面倒な事をしなくてもgcc/g++の初期設定でどうにかならないのか?と検索してみた。
結論として、gcc/g++の設定ファイルspecsをいじるのが良いという結論に達した。
https://nicstange.wordpress.com/tag/rpath/
http://stackoverflow.com/questions/17220872/linking-g-4-8-to-libstdc
http://www.mingw.org/wiki/HOWTO_Use_the_GCC_specs_file
https://gcc.gnu.org/onlinedocs/gcc-5.3.0/gcc/Spec-Files.html
gccのspecsをファイルに展開。
$ /usr/local/gcc-5.3/bin/gcc -dumpspecs > specs
ファイルspecsを編集.
初期状態
*link_libgcc:
%D
修正後 (2016/8/3修正)
*link_libgcc:
%{!static:%{!static-libgcc:-rpath /usr/local/gcc-5.3/lib}} %D
ファイルspecsを以下に差し込む。
$ cp specs /usr/local/gcc-5.3/lib/gcc/i686-pc-linux-gnu/5.3.0/specs
注意:上記例は単一アーキテクチャ32bit環境のみを想定している。gccが64bit/32bit両対応の場合はこの記述では不十分。
この作業により、g++の引数になにも指定せず、環境変数LD_RUN_PATH, LD_LIBRARY_PATHなどを用いる事なく、正しいlibstdc++.so.6が動的にリンクされるようになった。
$ /usr/local/gcc-5.3/bin/g++ -std=c++11 map.cc
$ ldd ./a.out
linux-gate.so.1 => (0x00955000)
libstdc++.so.6 => /usr/local/gcc-5.3/lib/libstdc++.so.6 (0x006d4000)
libm.so.6 => /lib/i686/nosegneg/libm.so.6 (0x00361000)
libgcc_s.so.1 => /usr/local/gcc-5.3/lib/libgcc_s.so.1 (0x008f5000)
libc.so.6 => /lib/i686/nosegneg/libc.so.6 (0x0019f000)
/lib/ld-linux.so.2 (0x0017d000)
なお、gcc-6.1用のspecsは以下の修正をすればよい。(2016/8/3修正)
*link_libgcc:
%{!static:%{!static-libgcc:-rpath /usr/local/gcc-6.1/lib}} %D
5.複数バージョンのgccの切り替え
今回の環境では複数バージョンのgcc(4.4.7, 5.3, 6.1)が混在する事になる。
gccの切り替えだが、多くのサイトでは環境変数pathを書き換えているが、OSで提供されているmodulesという機能を使ってみる事にする。
http://hpcmemo.hatenablog.com/entry/2013/04/04/113642
http://modules.sourceforge.net/
http://modules.sourceforge.net/man/modulefile.html
本機能はCentOS6.7の標準インストールではおそらく入ってない。次のコマンドで導入できる。
$ yum install environment-modules # <- ほかにも必要かもしれないが未確認
次のmodulesの設定ファイルを作成する。
/etc/modulefiles/gcc5x
#%Module 1.0
#
# gcc-5.X module for use with 'environment-modules' package:
#
conflict gcc6x
prepend-path PATH /usr/local/gcc-5.3/bin
/etc/modulefiles/gcc6x
#%Module 1.0
#
# gcc-6.X module for use with 'environment-modules' package:
#
conflict gcc5x
prepend-path PATH /usr/local/gcc-6.1/bin
インストールされているmodulesの確認。
$ module avail
------------------------------------- /usr/share/Modules/modulefiles --------------------------------------
dot module-git module-info modules null use.own
-------------------------------------------- /etc/modulefiles ---------------------------------------------
gcc5x gcc6x
gccの切り替え。
$ gcc --version # デフォルトは4.4.7
gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16)
(出力省略)
$ module add gcc5x # gcc-5.3に切り替え
$ gcc --version
gcc (GCC) 5.3.0
(出力省略)
$ module unload gcc5x # 解除(unload)
$ gcc --version # デフォルトに戻った。
gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16)
(出力省略)
$ module add gcc6x # gcc-6.1に切り替え
$ gcc --version
gcc (GCC) 6.1.0
(出力省略)
以上のように自在に切り替えが出来る。常に変更する場合は ~/.bashrcに加筆すればよい。
# .bashrc
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# 加筆
module add gcc5x
以上。