【環境】
Ubuntu16.04にUbuntu18.04で使用されているglibcを適用させる。
現在、Ubuntu16.04はglibc-2.23でUbuntu18.04はglibc-2.27だった。
【前提知識】
ELFローダ
ELFローダのパスはバイナリに埋め込まれている。
通常、そのパスはシンボリックリンクになっていて、その先を変更することで全バイナリをロードするローダを変更することができる。
バイナリ実行時にはELFローダのバージョンと同一のバージョンのglibc(ELFローダと同一ディレクトリ上のglibc)が使用される。
【手順1】glibcソースコードのダウンロード
https://ftp.gnu.org/gnu/glibc/ からwgetなどで落とすか、もしくは新しいバージョンのディストリビューションでapt source libc6
して落としたソースコードをscpなどで送信
$ cd ~/src/
$ wget https://ftp.gnu.org/gnu/glibc/glibc-2.27.tar.gz
$ tar zxvf glibc-2.27.tar.gz
$ cd ~/src/
$ apt source libc6
$ scp -r ./glibc-2.27/* hoge@123.123.123.123:~/src/glibc-2.27/
【手順2】ビルド用のディレクトリを作成
$ mkdir ~/src/build
【手順3】install先のディレクトリを作成
mkdir ~/program/glibc-2.27
【手順4】configure
buildディレクトリに移動し、そこからglibc-2.27ディレクトリ上のconfigureを叩く。
../glibc-2.27/configure --prefix=/home/miyagaw61/program/glibc-2.27
でも大丈夫だとは思うが、Ubuntu18でapt sourceで入れたlibc6のビルド方法とできるだけ近づけたオプションでUbuntu16でビルドしたい、といった場合は、Ubuntu18上のglibc-2.27ディレクトリでdpkg-buildpackage -us -uc
を実行し、ちょっとしたらCtrl+Cで止め、find -name *config.log*
で出力される./build-tree/amd64-libc/config.log
に書いてあるオプションを参考にビルドすると良い。
今回は
../glibc-2.27/configure --host=x86_64-linux-gnu --build= --prefix=/usr --enable-add-ons=libidn, --without-selinux --enable-stackguard-randomization --enable-stack-protector=strong --enable-obsolete-rpc --enable-obsolete-nsl --with-pkgversion=Ubuntu GLIBC 2.27-3ubuntu1 --with-bugurl=https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs --with-headers=/home/miyagaw61/src/glibc-2.27/glibc-2.27/debian/include --enable-kernel=3.2 --with-selinux --enable-systemtap --enable-multi-arch --enable-static-pie
だった。
Ubuntu16でこれをそのまま実行してしまうと、色々と問題が発生するため、所々修正する必要がある。
修正後はこちら。
../glibc-2.27/configure --host=x86_64-linux-gnu --build= --prefix=/home/miyagaw61/program/glibc-2.27 --enable-add-ons=libidn, --without-selinux \
--enable-stackguard-randomization --enable-stack-protector=strong --enable-obsolete-rpc --enable-obsolete-nsl \
--with-pkgversion=Ubuntu GLIBC 2.27-3ubuntu1 --with-bugurl=https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs \
--enable-kernel=3.2 --with-selinux --enable-systemtap --enable-multi-arch
変更箇所:
・--prefixの値の修正
・--with-headersの削除
・--enable-static-pieの削除
【手順5】install
そのままmake
とmake install
を実行する。
【手順6】不足しているライブラリを追加
このglibc-2.27には結構不足しているライブラリがあり、それらを既存のライブラリで補完する必要がある。
一番最初に述べた「バイナリ実行時にはELFローダのバージョンと同一のバージョンのglibc(ELFローダと同一ディレクトリ上のglibc)が使用される。」という文章を思い出してほしい。/lib/x86_64-linux-gnuディレクトリや/usr/lib/x86_64-linux-gnuディレクトリに存在していて~/program/glibc-2.27ディレクトリに存在しない名前のファイルは、全て~/program/glibc-2.27ディレクトリに集めなければならない。
$ cd ~/program
$ cp -a /lib/x86_64-linux-gnu lib-glibc.org
$ cp -a /usr/lib/x86_64-linux-gnu usr-lib-glibc.org
$ sudo chown miyagaw61:miyagaw61 -R lib-glibc.org usr-lib-glibc.org
$ cp -a usr-lib-glibc.org new-glibc
$ cp -a lib-glibc.org/* new-glibc/
$ cp -a glibc-2.27/lib/* new-glibc/
$ cp -a new-glibc/* glibc-2.27/lib/
$ rm -rf new-glibc
【手順7】root化
この後、ダイナミックリンクされている全てのバイナリが動かなくなるので、root状態でbusyboxが使えるようにするため、事前にrootになっておく(busyboxにsudoが存在しないため)。
【手順8】glibcのパスを変更後ldconfig
ld.so.confを編集し、ldconfig
でld.so.cacheに反映させる。
# echo "/home/miyagaw61/program/glibc-2.27/lib" > tmp
# cat /etc/ld.so.conf >> tmp
# mv tmp /etc/ld.so.conf
# ldconfig
この時点で、現在のELFローダのバージョンとld.so.cacheに設定されているライブラリのバージョンの不整合で、ダイナミックリンクされている全てのバイナリは正常に動かなくなる(ELFローダ:2.23, ライブラリ:2.27)。
【手順9】busyboxを用いてELFローダのバージョンをライブラリに合わせる
ELFローダのシンボリックリンク先を新しいバージョンのELFローダのパスに変更することで、ELFローダのバージョンをライブラリのバージョンと同じにすることができる。
busybox ln -sf /home/miyagaw61/program/glibc-2.27/lib/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2
これで、新しいバージョンのglibcを使用して全てのバイナリを正常に動かすことが可能になる。
【補足】元のlibcの退避には注意
「なんかの拍子で/lib/x86_64-linux-gnuとかが使われたら面倒くさいので、一応わかりやすくするために/lib/x86_64-linux-gnu.orgとかに退避させておこう」とかすると、sudoをするたびにsudo: policy plugin failed session initializationというエラーが出るようになってしまう。原因はわからないが、
$ sudo su
# cd /lib/
# mv x86_64-linux-gnu x86_64-linux-gnu.org
# cp -a /home/miyagaw61/program/glibc-2.27/lib x86_64-linux-gnu
とすれば大丈夫ではあった。
/usr/lib/x86_64-linux-gnuはも同様に、普通に退避させるだけだとapt installでエラーを吐くようになる。
$ sudo su
# cd /usr/lib/
# mv x86_64-linux-gnu x86_64-linux-gnu.org
# cp -a /home/miyagaw61/program/glibc-2.27/lib x86_64-linux-gnu
【追記】linux-headers-$(uname -r)の再インストール
このままだと、ビルド時に問題が発生する可能性があることが判明した。
例えばWalBというOSSのビルドを例にすると
$ make
DEBUG=0 DYNAMIC_DEBUG=0 OVERLAP=1
DEBFLAGS=-O2 -g -DWALB_OVERLAPPED_SERIALIZE
make clean
make[1]: Entering directory '/home/miyagawa/src/github.com/miyagaw61/walb/module'
rm -f build_date.h
rm -f version.h
cd test; rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers
make[1]: Leaving directory '/home/miyagawa/src/github.com/miyagaw61/walb/module'
make buildmodule
make[1]: Entering directory '/home/miyagawa/src/github.com/miyagaw61/walb/module'
cat build_date.h.template | sed "s/XXXXX/`env LC_ALL=C date`/" > build_date.h
make -C /lib/modules/4.15.0-24-generic/build M=/home/miyagawa/src/github.com/miyagaw61/walb/module modules
make[2]: Entering directory '/usr/src/linux-headers-4.15.0-24-generic'
make[2]: *** No rule to make target 'modules'. Stop.
make[2]: Leaving directory '/usr/src/linux-headers-4.15.0-24-generic'
Makefile:96: recipe for target 'buildmodule' failed
make[1]: *** [buildmodule] Error 2
make[1]: Leaving directory '/home/miyagawa/src/github.com/miyagaw61/walb/module'
Makefile:90: recipe for target 'default' failed
make: *** [default] Error 2
というようなエラーが出る。
次のコマンドを実行することで対応することに成功した。
$ sudo apt install linux-headers-$(uname -r) --reinstall