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

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

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

QT_VISIBILITY_AVAILABLE is not available

ところが解決は以外なところで見つかりました。

とりあえずビルドできないlibdbusmenu-qtは放置して,本命のKDE-4.8.3の一部であるkdelibsをビルドすればどうなるだろう,と試してみたところ,CMakeがビルド環境をチェックしていく途中で以下のようなエラーが表示されました。

Run Build Command:/usr/bin/make "cmTryCompileExec658130683/fast"
/usr/bin/make -f CMakeFiles/cmTryCompileExec658130683.dir/build.make \
  CMakeFiles/cmTryCompileExec658130683.dir/build
make[1]: ディレクトリ `/nfs/Srcs/K/KDE/4.8.3/Kdelibs/build/CMakeFiles/CMakeTmp' に入ります
/usr/bin/cmake -E cmake_progress_report \
 /nfs/Srcs/K/KDE/4.8.3/Kdelibs/build/CMakeFiles/CMakeTmp/CMakeFiles 1
....
/usr/bin/g++  -isystem /usr/include -m64   -Wnon-virtual-dtor \
 -Wno-long-long -ansi -Wundef -Wcast-align -Wchar-subscripts -Wall \
 -W -Wpointer-arith -Wformat-security -fno-exceptions -DQT_NO_EXCEPTIONS...

/nfs/Srcs/K/KDE/4.8.3/Kdelibs/build/CMakeTmp/check_qt_visibility.cpp:5:3: \
 エラー: #error QT_VISIBILITY_AVAILABLE is not available
make[1]: ディレクトリ `/nfs/Srcs/K/KDE/4.8.3/Kdelibs/build/CMakeFiles/CMakeTmp' から出ます
make[1]: *** [CMakeFiles/cmTryCompileExec658130683.dir/check_qt_visibility.cpp.o] エラー 1
make: *** [cmTryCompileExec658130683/fast] エラー 2

CMake Error at cmake/modules/FindKDE4Internal.cmake:1295 (message):
  Qt compiled without support for -fvisibility=hidden.  This will break
  plugins and linking of some applications.  Please fix your Qt installation
  (try passing --reduce-exports to configure).
 Call Stack (most recent call first):
  CMakeLists.txt:50 (find_package)

これは今まで見た記憶のないタイプのエラーでした。原因は,エラーメッセージにあるように,Qtが-fvisibility=hiddenのオプションを付けてビルドされていないことのようです。

メッセージによると,Qtが-fvisibility=hiddenを付けずにビルドされている場合,プラグインの読み込みやアプリケーションのリンク時に失敗することがある,とのことなので,どうやらlibdbusmenu-qtのリンクに失敗していたのもこれが原因だったようです。

メッセージには,configure時に--reduce-exportsという指定をすればいい,と書いてあるので,libdbusmenu-qtのビルドに問題が無かった古いQt-4.7.1ではそのような指定をしていたのだろうか…,と調べ直してみると,configureに与えるオプション指定ではなく,何気なく外していたconfigure_REDUCE_EXPORTS.patchというパッチで,configureスクリプトのデフォルト値を変更していたのでした。

リスト1 configure_REDUCE_EXPORTS.patch

--- qt-kde-qt/configure 2010-09-07 06:05:01.000000000 +0900
+++ build/configure     2011-02-13 22:29:59.000000000 +0900
@@ -730,7 +730,7 @@
 CFG_PRECOMPILE=auto
 CFG_SEPARATE_DEBUG_INFO=auto
 CFG_SEPARATE_DEBUG_INFO_NOCOPY=no
-CFG_REDUCE_EXPORTS=auto
+CFG_REDUCE_EXPORTS=yes
 CFG_MMX=auto
 CFG_3DNOW=auto
 CFG_SSE=auto

なるほどこれだったのか,と合点して,このパッチをあてるようにQt-4.8.1をビルドし直してみたところ,新しいQt-4.8.1の環境下では,無事libdbusmenu-qtもリンクできるようになり,kdelibs-4.8.3のビルドも可能になりました。

GCCの-fvisibilityオプション

さて,とりあえず正しく動作しそうなQt-4.8.1はビルドできたものの,問題の-fvisibility=hiddenとは一体どういう意味のオプションなのでしょうか? Google等で調べたところ,この指定はGCCが生成するELF形式のバイナリファイルのVisibilityという属性を制御する機能だそうです。

ELFとはExecutable and Linking Formatの略で,コンパイラが生成するバイナリファイルの形式の1つです。最初期のLinuxでは,よりシンプルなa.outと呼ばれる形式を採用していましたが,a.out形式には共有ライブラリを作成しにくいといった問題があったため,USL(UNIX System Laboratories)が開発し,SysV R4 UNIXやSolarisが採用していたELF形式に変更した歴史があります。

ELF形式のバイナリファイルは,⁠Executable and Linking Format⁠という名称通り,実行形式として使うことも可能だし,他のバイナリファイルとリンクして使うことも可能になっており,そのためELF形式のバイナリファイルでは,そのファイルが持っているシンボル(関数や変数の名前)を外部に公開するかどうかをVisibilityという属性を使って細かく制御できるようになっています。

GCC付属のドキュメント(gcc.info)によると,-fvisibilityは,このVisibility属性のデフォルト値を指定するためのオプションで,-fvisibility=hiddenと指定すると,外部に公開することをソースコード中で明示したシンボル以外は外部から見えなくなるように各シンボルのVisibility属性が設定されます。

-fvisibilityオプションを指定しない場合は-fvisibility=defaultと解釈され,この機能を持たない古いGCCでビルドした場合と同様,全てのシンボルが外部に公開されるようになります。

上記ドキュメントによると,大規模なライブラリ,特にクラス継承等で膨大なシンボルを扱うことになるC++で書かれたライブラリの場合,公開するシンボルを記録したテーブルが巨大になり,シンボルの解決に時間がかかってアプリケーションの起動が遅くなるといった問題が起きがちなので,新しく共有ライブラリを開発する場合は,ビルド時に-fvisibility=hiddenを指定して,ソースコードで明示したシンボル以外は公開しないようにすることを強く推奨していました。

今回ハマったlibdbusmenu-qtの問題も,どうやらこのVisibility属性の設定ミスが原因で,-fvisibility=hiddenを指定せずにビルドしたQtを使ったので,本来は外部に公開すべきではないシンボルまで見えてしまってリンカが混乱し,libdbusmenu-qt自身のライブラリに含まれているシンボルを正しく処理できずに"undefined symbol"というエラーになってしまったようです。

それでは,このオプション指定の有無で,どれくらい公開されているシンボル数が変わるのだろう? と思って,上記パッチをあてた場合とそうでない場合それぞれのバージョンのQt-4.8.1を作って調べてみました。

-fvisibility=hiddenを付けない場合のlibQtCore.so.4.8.1は

$ nm -C -D NG/usr/lib64/libQtCore.so.4.8.1 | wc -l
6575

と,6575のシンボルが表示されていました。

一方,このオプションを付けた場合は,

$ nm -C -D OK/usr/lib64/libQtCore.so.4.8.1 | wc -l
4300

となり,付けない場合に比べてシンボル数は2300ほど減少しました。

具体的にどのようなシンボルが見えなくなっているのだろう? と思って,それぞれのライブラリからシンボル名を抽出して比較してみました。

$ nm -C -D NG/usr/lib64/libQtCore.so.4.8.1 | cut -f3- -d' ' | sort > NG_symbols.dat
$ nm -C -D OK/usr/lib64/libQtCore.so.4.8.1 | cut -f3- -d' ' | sort > OK_symbols.dat
$ diff -u NG_symbols OK_symbols
--- NG_symbols.dat      2012-05-26 08:21:41.865942026 +0900
+++ OK_symbols.dat      2012-05-26 08:21:49.148446539 +0900
@@ -224,86 +224,6 @@
                w __cxa_finalize
                w __gmon_start__
                w __pthread_unwind_next
-BackEase::copy() const
-BackEase::value(double)
-BackEase::~BackEase()
...

両者の違いをdiffで見ると,-fvisibility=hiddenを付けた場合はBackEase::copy()やBackEase::value(double)といった関数が見えなくなっているようです。

両者の違いを見ていて,なるほど,と思ったのは,このオプションを指定したライブラリでは,名前の一部にstaticやPrivateという文字列が入った,本来ローカルのみで利用すべきであろう関数が見えなくなっていることでした。

 QAbstractAnimation::qt_metacast(char const*)
-QAbstractAnimation::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)
 QAbstractAnimation::resume()
...
 QAbstractAnimation::staticMetaObject
-QAbstractAnimation::staticMetaObjectExtraData
 QAbstractAnimation::stop()
...
 QAbstractAnimation::~QAbstractAnimation()
-QAbstractAnimationPrivate::setState(QAbstractAnimation::State)
-QAbstractAnimationPrivate::~QAbstractAnimationPrivate()
-QAbstractAnimationPrivate::~QAbstractAnimationPrivate()
-QAbstractAnimationPrivate::~QAbstractAnimationPrivate()
 QAbstractConcatenable::convertFromAscii(char const*, int, QChar*&)
...

これらの関数がソースコード中でどのように定義されているのかまでは調べていませんが,GCC WikiのVisibilityに関する項目では,Visibility属性を設定するためのクロスプラットフォームで使えるマクロを定義しておき,外部から参照してもいい関数はそのマクロを使って属性をdefaultに,そうでない関数はコンパイル時の-fvisibility=hiddenオプションで属性をhiddenにして外部から見えないようにする,といった方法が推奨されているので,恐らくQt-4.8.1のソースコードでもそのような設定になっているのでしょう。

上記GCC Wikiに説明されているように,この機能は元々,クラス継承等でシンボル数が膨大になりがちなC++を念頭に開発された機能で,GCC-3.6のころにパッチとして提案され,4.0以降ではソースツリーに取り込まれて,正式な機能として利用可能になっていたそうです。

個人的に,C++は規模が大きすぎて手に負えない,と敬遠しがちだったこともあって,このような便利な機能が,ずいぶん前から採用されていることをすっかり見落していたので,今回の経験は「目からウロコ」の例となりました。

ディストリビューションのまとめ役を務めるには,日々の修行が欠かせないようです……。

著者プロフィール

こじまみつひろ

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

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