玩式草子─ソフトウェアとたわむれる日々

第34回 C++とGCCの-fvisibilityオプション

この記事を読むのに必要な時間:およそ 6.5 分

WindowsやMac OS Xのように,1つの企業が開発環境や標準ライブラリを管理して,必要とあれば全ての開発者に新しいツールやライブラリを強制できる商用OSに比べて,世界中に分散した開発者がボランティアとして開発に参加しているOSSの世界では,新しいツールやライブラリが広く利用されるには過去との互換性を保つことが重要になります。

そのため,GCCやGlibc,X Window Systemといった主要なソフトウェアでは,過去のバージョンを用いていたソフトウェアも動作するように,バージョンアップを繰り返して中身がそっくり変わってしまっても,APIやABIは過去のバージョンとの(後方)互換性を保つように努力しています。

API(Application Program Interface)の互換性は,関数名や呼び出す際の引数等を同じにして,それらを使っているソフトウェアのソースコードを修正しなくてもコンパイルし直せば動作するレベルの互換性,ABI(Application Binary Interface)の互換性は,APIに加えてライブラリの名前やメジャーバージョン番号も同じにして,コンパイルし直す必要すら無くしたレベルの互換性を意味します。

そのおかげで,最近では,OSの基盤となるこれらソフトウェアを更新する際も,以前のように互換性についてあれこれ調査する必要が無くなりました。その一方で,README等のファイルを調べるのをサボりがちになったので,バージョンアップ時に採用された新機能等を見落してしまうことも多くなりました。今回は,そのような新機能がらみでハマった話題を紹介しましょう。

libQtに起因する謎のエラー

KDE回りを新しいバージョンに追従するために,KDEのベースとなっているQtを4.7.1から4.8.1に更新したところ,Qtを使うソフトウェアのビルドができなくなりました。

具体的には,最近のデスクトップ環境の基盤となっているDBus上にメニュー構造のデータを流すためのDBusmenuライブラリのQt版,libdbusmenu-qt-0.9.2をビルドしようとすると,リンク時に「参照しているシンボルが見つからない("undefined reference")⁠というエラーになってしまいました。

$ make
...
[ 51%] Built target dbusmenu-qt
Linking CXX executable dbusmenuexportertest
CMakeFiles/dbusmenuexportertest.dir/dbusmenuexportertest.cpp.o: 
     In function `DBusMenuExporterTest::testGetSomeProperties()':
dbusmenuexportertest.cpp:(.text+0x1df7): undefined reference to 
   `DBusMenuExporter::DBusMenuExporter(QString const&, QMenu*, 
     QDBusConnection const&)'
dbusmenuexportertest.cpp:(.text+0x275b): undefined reference to 
   `DBusMenuExporter::~DBusMenuExporter()'
dbusmenuexportertest.cpp:(.text+0x27df): undefined reference to 
   `DBusMenuExporter::~DBusMenuExporter()'

このエラーメッセージは,⁠dbusmenuexportertest.cpp.oというオブジェクトファイルを実行形式にするために他のライブラリとリンクしようとしたところ,リンクしようとしているどのライブラリにもDBusMenuExporter::DBusMenuExporter(QString...)等の関数が見つからないのでリンクできなかった」という意味です。

このソフトウェアはC++で開発されているので,厳密には「DBusMenuExporterクラス」「DBusMenuExporterメソッド」と呼ぶべきでしょうが,煩雑なので「関数」と呼んでおきます。

今までの経験では,この種のトラブルの原因は,必要なライブラリの指定がMakefileの中で抜けていたり,ライブラリのあるディレクトリが正しく指定されていないことでした。

そこでまずMakefileの内容をチェックしてみましたが,このソフトウェアはCMakeを利用しているためMakefileの書式が独特で,どの部分で問題のリンクを実行しているのかがよくわかりません。

CMakeは,"Cross-platform"を特徴とするソフトウェアのビルドシステムで,Unixだけではなく,Mac OS XやMS Windows上でも動作するようになっています。CMakeは,自身が直接ビルドを制御するのではなく,CMakeLists.txtという設定ファイルを元にそれぞれの環境に応じたMakefileやプロジェクトファイルを生成するようになっており,Linux上では,実際のビルドはCMakeが作成したMakefileを用いてmakeコマンドが担当します。

ざっと眺めたところでは,CMakeで生成されたMakefileでは,今回知りたいコンパイルやリンク時のオプション指定等をMakefile自体ではなく,Makefileから参照するファイル群に記述しているようなので追いかけるのが面倒です。そこでmakeコマンドが実行している内容を詳細に表示させるためのVERBOSE=1というオプションを指定して,どのようなコマンドを実行しているのか表示させることにしました。

$ make VERBOSE=1
/usr/bin/cmake -H/nfs/Srcs/L/Libs/Dbusmenu-qt/libdbusmenu-qt-0.9.2 \
   -B/nfs/Srcs/L/Libs/Dbusmenu-qt/build --check-build \
   -system CMakeFiles/Makefile.cmake 0
usr/bin/cmake -E cmake_progress_start \
  /nfs/Srcs/L/Libs/Dbusmenu-qt/build/CMakeFiles \
  /nfs/Srcs/L/Libs/Dbusmenu-qt/build/CMakeFiles/progress.marks \
  make -f CMakeFiles/Makefile2 all
....
Linking CXX executable dbusmenuexportertest
cd /nfs/Srcs/L/Libs/Dbusmenu-qt/build/tests && \
  /usr/bin/cmake -E cmake_link_script \
  CMakeFiles/dbusmenuexportertest.dir/link.txt --verbose=1
/usr/bin/g++  -isystem /usr/include -m64 \
  CMakeFiles/dbusmenuexportertest.dir/dbusmenuexportertest.cpp.o \
  CMakeFiles/dbusmenuexportertest.dir/testutils.cpp.o  \
  -o dbusmenuexportertest -rdynamic -lQtGui -lQtCore -lQtDBus -lQtTest \
  ../src/libdbusmenu-qt.so.2.6.0 -lQtGui -lQtCore -lQtDBus \
  -Wl,-rpath,/nfs/Srcs/L/Libs/Dbusmenu-qt/build/src
CMakeFiles/dbusmenuexportertest.dir/dbusmenuexportertest.cpp.o: \
 In function `DBusMenuExporterTest::testGetSomeProperties()':
dbusmenuexportertest.cpp:(.text+0x1df7): undefined reference to \
 `DBusMenuExporter::DBusMenuExporter(QString const&, QMenu*, \
  QDBusConnection const&)'
...

この結果を見ると,リンク時のコマンドは

/usr/bin/g++  -isystem /usr/include -m64 \
  CMakeFiles/dbusmenuexportertest.dir/dbusmenuexportertest.cpp.o \
  CMakeFiles/dbusmenuexportertest.dir/testutils.cpp.o  \
  -o dbusmenuexportertest -rdynamic -lQtGui -lQtCore -lQtDBus -lQtTest \
  ../src/libdbusmenu-qt.so.2.6.0 -lQtGui -lQtCore -lQtDBus \
  -Wl,-rpath,/nfs/Srcs/L/Libs/Dbusmenu-qt/build/src

となっているようで,実際にこのコマンドをスクリプトにして実行しても同じエラーになりました。一方,問題となっているDBusMenuExporter::DBusMenuExporter(QString...)という関数はどのライブラリが持っているのだろう…,と調べると,このソフトウェア自体が作っているlibdbusmenu-qt.so.2.6.0の中にありました。

$ nm  -C ../src/libdbusmenu-qt.so.2.6.0 | grep DBusMenuExporter::DBusMenuExporter
0000000000009882 t DBusMenuExporter::DBusMenuExporter\
  (QString const&, QMenu*, QDBusConnection const&)
0000000000009882 t DBusMenuExporter::DBusMenuExporter\
  (QString const&, QMenu*, QDBusConnection const&)

さて,そうなると上記リンク時のコマンドでこのライブラリは正しく参照されているなずなのに,なぜ"undefined reference"エラーになるのでしょう? ライブラリを参照する順番を変えてみたり,libdbusmenu-qt.so.2.6.0を明示的にライブラリとして参照させたりしても状況は変わりませんでした。

おかしいな…,と思ってQtを古いバージョン(4.7.1)に戻してみると,この部分も問題なく通ってビルドは終了します。どうやら問題はlibdbusmenu-qtではなくQtの側にありそうだ,ということまでは分かりましたが,Qt-4.8.1の何が問題なのかは分かりません。

Qt-4.8.1のビルドでは,以前当てていたローカルなパッチは外してBLFSのパッチやオプション指定を流用するようにしているので,ソースコードレベルで問題があるようにも思えません。当初は,nmコマンドでライブラリを調べるとシンボルが見つからないと表示されることが原因か,とも考えて

$ nm /usr/lib64/libQtCore.so.4.8.1 
nm: /usr/lib64/libQtCore.so.4.8.1: no symbols

ライブラリから不要なシンボルを取り除くコマンドをstrip --un-neededからstrip --debugに変更し,シンボルを見えるようにビルドし直してみても同じところでリンクに失敗します。

後で知ったところでは,strip --un-neededしたバイナリファイルでは,通常のシンボルは削除されるものの,共有ライブラリのリンクに必要な動的シンボルは記録されているので,nmに-Dオプションを与えれば,リンク時に利用される動的シンボルは表示されます。

あれこれ思いつく対策は試してみましたが,どうにも解決できないので,Qt-4.8.1への更新は見送ろうか…,とも思い始めました。

著者プロフィール

こじまみつひろ

Plamo Linuxとりまとめ役。もともとは人類学的にハッカー文化を研究しようとしていたものの,いつの間にかミイラ取りがミイラになってOSSの世界にどっぷりと漬かってしまいました。最近は田舎に隠棲して半農半自営な生活をしながらソフトウェアと戯れています。

URLhttp://www.linet.gr.jp/~kojima/Plamo/index.html

コメント

コメントの記入