追記(2020年5月6日)
以下の挙動は5.7.28で確認をしていましたが、5.7.30で修正されています。
結論
同じシンボルをGLOBALで公開している複数の共有ライブラリをdlopenを使って動的にロードした場合に、解決されるシンボルがファイル間で前後することがある。
ということが原因のようです。
サンプルコード
https://github.com/usualoma/break-zlibにサンプルコードがあります。
発生した問題
以下のようなDockerfileで環境を構築したときに
「(perlでImage::Magickを使って)PNG画像のサムネイルが生成できない」という問題が発生し、条件を絞り込んでいくとどうも「DBD::mysqlをImage::Magickよりも先に読み込んだ場合に生成できなくなる」ということが分かりました。ポイントは、
- MySQL Yum Repositoryのmysql-community-libs-compatでlibmysqlclient.so.18をインストールしている
- libmysqlclient.so.18にリンクされた共有ライブラリを動的な環境で読み込んでいる
というころにあるようです。
この環境では、以下のようにDBD::mysqlを先に読み込んだ場合には失敗し、後に読み込んだ場合には成功します。
さらに(識者からの助言をもらいつつ)もう少し追っていくと、PNG画像の読み込み時のgzipの初期化に失敗していることが分かりました。
以下のように、zlibを直接呼び出すperlモジュールでも問題が発生します。
発生条件
色々試した結果、https://github.com/usualoma/break-zlib/tree/master/srcにあるようなコードで、
- libmysqlclient.so.18にリンクした共有ライブラリを作成
- libz.so.1にリンクした共有ライブラリを作成
- それらを、libmysqlclient ->libz の順に動的にロードする
というやり方で発生させることができました。
以下のようにLD_PRELOAD使った場合には発生しなかったので「ロードの順序」だけではなく、「動的にロードすること」が条件になりそうです。
発生した理由
$ ldd /usr/lib64/mysql/libmysqlclient.so.18
の結果にlibz.so.1が含まれてもいるのですが、
$ readelf -Ws /usr/lib64/mysql/libmysqlclient.so.18.1.0
の結果にも「inflateReset」のようなlibzのシンボルがGLOBALとして含まれており、結果としてlibzの共有ライブラリとして利用できるようになっているようです。
gdbでブレークポイントを設定して見ていったところ、
- inflateReset2は、libz.so.1のもの
- その中で呼ばれるinflateResetは、libmysqlclient.so.18のもの
というようになっていて、それぞれに含まれるzlibのバージョンの差異によりエラーが発生しているようでした。
mysql.specを見る限り、WITH_EMBEDDED_SHARED_LIBRARYは明示的に1を指定しているようなので、libzを含むところまでは意図してやっていそうでしたが、GLOBALになっている理由はよく分かりませんでした。(mysql-community-libsからインストールされる/usr/lib64/mysql/libmysqlclient.so.20.3.15には、libzが含まれているものGLOBALではありませんでした。)
感想
mysql-community-libs-compatをインストールすると依存関係でmysql-community-libsが入り、/usr/lib64/mysql/libmysqlclient.soがlibmysqlclient.so.20を指すようになるので、MySQL Yum Repositoryとしてはlibmysqlclient.so.20を推奨っぽいので、可能な場合にはそちらにリンクしたモジュールをビルドし直した方がいいのかもしれません。
ビルドをしたくない場合、CentOSのパッケージで済まそうとすると基本的にはlibmysqlclient.so.18にリンクされていて上の状況は割と発生しそうな気がするので、その場合にはlibz.so.1を先にロードしてあげたりするなどの対応が必要かもしれません。