続・玩式草子 ―戯れせんとや生まれけん―

第43回風が吹けば桶屋が儲かる

「風が吹けば桶屋が儲かる」は江戸時代から伝わる言い回しで、一見、何の関係も無さそうなところにまで影響が及ぶ事例や、そのために使われる無理なこじつけを批判するような場面で使います。

元々の意味としては「風が吹けば土埃がたつ、すると土埃が目に入って目を病む人が増える、目を病む人の生業は三味線の師匠が多いから三味線が売れる、三味線には猫の皮を貼るから猫が狩られる、狩られて猫が少なくなると鼠が増える、鼠が増えると桶などが齧られる、ゆえに仕事が増えて桶屋が儲かる」という連鎖だそうです。まぁ、これは笑い話レベルのこじつけですが、最近、これに類したトラブルを経験したので紹介してみましょう。

mate-calcのSeg.fault

ある日mate-calcが動かなくなったという報告が届きました。"mate-calc"はMateデスクトップ環境で開発されている電卓ツールで、一見すると単純な電卓なものの、gnome-calcやOpenWindows Desktopのcalctoolにまで遡る歴史と、科学技術計算のみならず、通貨の交換レートや複利計算等の財務関連機能、GNU MPFR/MPRライブラリを用いた任意精度の浮動小数点演算機能などを秘めた高機能なソフトウェアです。

mate-calc
mate-calc

この報告には「あれ?」と思いました。というのも、先にリリースしたPlamo-7.4ではmate-calcは問題なく動いていたし、その後、バージョンアップ等はしていません。また、Plamo-7.4を元に適宜パッケージを更新している手元の環境でもmate-calcは問題なく動作しています。そこで、どうやらこれはmate-calc本体ではなくライブラリ等の周辺環境の方の問題だろうな、と考えました。

確認のためVirtualBox上の仮想環境にPlamo-7.4のリリース版をインストールし、"get_pkginfo"で更新されたパッケージ一式をダウンロードしたところ、すでに100ほどのパッケージが更新されていました。Plamo-7.4のインストール直後の環境ではmate-calcが問題なく動作することを確認した上で、この状態を「スナップショット」して保存し、更新されたパッケージをアップデートしてみたところ、確かにmate-calcがSeg.Faultして起動しません。

$ mate-calc
Segmentation fault

どこで落ちているのか確認するため"gdb"を使ってみても、落ちたアドレスこそわかるものの、該当するライブラリ等は"?? ( )"になっています。

$ gdb /usr/bin/mate-calc
GNU gdb (GDB) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
...
(gdb) run
Starting program: /usr/bin/mate-calc 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/libthread_db.so.1".
[New Thread 0x7ffff3aec640 (LWP 14533)]
[New Thread 0x7ffff32eb640 (LWP 14534)]
..
[Thread 0x7ffff22e9640 (LWP 14540) exited]

Thread 1 "mate-calc" received signal SIGSEGV, Segmentation fault.
0x00007ffff3d77af0 in ?? ()
(gdb)

コマンドの実行時、どのようなライブラリがメモリ空間のどこにロードされるかは、/proc/〈pid〉/mapsで確認することができます。"ps"コマンドでmate-calcのプロセス番号(pid)を確認すると14530でした。

$ ps ax | grep mate-calc
14524 pts/3    Sl+    0:00 gdb /usr/bin/mate-calc
14530 pts/3    tl     0:00 /usr/bin/mate-calc
14568 pts/5    S+     0:00 grep mate-calc

そこで/proc/14530/mapsを調べてみても、Seg.Faultが起きている"7ffff3d777af0"のあたりは空白になっています。

$ cat /proc/14530/maps | less
00400000-0040e000 r--p 00000000 08:02 4922350                            /usr/bin/mate-calc
0040e000-0042e000 r-xp 0000e000 08:02 4922350                            /usr/bin/mate-calc
0042e000-00442000 r--p 0002e000 08:02 4922350                            /usr/bin/mate-calc
...
7fffdad3e000-7fffdc000000 r--s 00000000 08:02 35293332                   /usr/share/anthy/anthy.dic
7fffdc000000-7fffdc07b000 rw-p 00000000 00:00 0 
...
7ffff3d2d000-7ffff3d2e000 r--p 0001e000 08:02 100669254                  /usr/lib/gconv/libJISX0213.so
7ffff3d2e000-7ffff3d2f000 rw-p 0001f000 08:02 100669254                  /usr/lib/gconv/libJISX0213.so
7ffff3d2f000-7ffff3df2000 rw-p 00000000 00:00 0           <-- 7ffff3d777af0 はこのあたり
7ffff3df2000-7ffff3df5000 r--p 00000000 08:02 100669654                  /lib/libnss_files-2.33.so
7ffff3df5000-7ffff3dfc000 r-xp 00003000 08:02 100669654                  /lib/libnss_files-2.33.so
7ffff3dfc000-7ffff3dfe000 r--p 0000a000 08:02 100669654                  /lib/libnss_files-2.33.so

予想通りSeg.Faultの原因は外部ライブラリらしいものの、具体的にどのライブラリがエラーを起こしているのかはわかりません。

状況確認

gdb等ではめぼしい情報が得られなかったので、もう少し状況証拠を集めてみることにしました。mate-calcは素のままのPlamo-7.4では問題なく、更新パッケージのアップデート後にエラーになるので、更新したパッケージのどれかが関わっていることは間違いありません。そこで100ほどの更新パッケージのどれが問題を起こしているのかを調べることにしました。

このような作業にはVirtualBoxのスナップショット機能が便利です。まずは一度、スナップショットを記録していたインストール直後の状態に戻し、そこから更新パッケージをアップデートしていって、どこでmate-calcがエラーになるかを調べます。

もっとも100くらいあるパッケージを1つずつ調べていくと時間がかかりすぎるので、とりあえずPlamo Linuxのパッケージカテゴリー単位でチェックしました。mate-calcはMateデスクトップ用の電卓ソフトウェアなので、まずは関係がありそうなX回りのパッケージや関連ライブラリ回りから試したものの、mate-calcがSeg.faultする気配はありません。

「うーん……」と首を捻りながらチェックを続けていくと、01_minimumというカテゴリのパッケージを更新するとmate-calcがSeg.Faultするようになりました。この01_minimumはPlamo Linuxを運用する際に必要になるパッケージを集めたカテゴリです。

Plamo Linuxでは、カーネルやglibc、bashといった必須のパッケージは00_baseというカテゴリに収め、このカテゴリをインストールすれば最低限のLinux環境が使えるように設計しています。しかしながら、このカテゴリだけではログインしてbashを使うくらいの機能しかなく、外部と通信したり(openssh⁠⁠、エディタ(emacs/vim)を使ったり、スクリプト言語(Python/Perl)を使うためには01_minimumカテゴリのパッケージが必要になります。

とりあえず01_minimumにある更新パッケージをインストールすればトラブルが再現することがわかったので、次はどのパッケージが原因になっているのかを調べます。そのためにはまたスナップショット機能でインストール直後の状態に戻し、01_minimum以下のパッケージを1つずつ更新して問題が起きるかをチェックしました。その結果、gnutlsを最新の3.7.7に更新するとmate-calcがSeg.Faultすることが判明しました。念のため再度スナップショットを戻して調べたところ、他のパッケージとは関係なく、gnutlsパッケージを3.7.7に更新するだけでmate-calcがSeg.Faultしました。

状況は確認できたものの、その結果にはさらに首を捻ることになりました。なぜならgnutlsは通信経路を暗号化するソフトウェアであるのに対し、mate-calcは電卓ソフトウェアで両者の間には何の関連もありません。実際、mate-calcが必要とするライブラリとgnutlsが提供するライブラリの間にも何ら接点はありません。さてこれはいったいどうしたものでしょう?

押してダメなら引いてみる

とりあえずmate-calcのSeg.Faultの原因がgnutlsパッケージの更新だ、ということはわかったものの、両者の間に接点が無いので、どうすれば解決するか見当がつきません。もしかしてバージョン依存かも…… とそれぞれ少し前のバージョンに遡って試したところ、mate-calcは1.24に戻せばgnutls-3.7.7でも動作し、gnutlsは3.7.4ならばmate-calc 1.26でも大丈夫、というあたりまではチェックできました。

そこでこれらのバージョンのChangeLogあたりを眺めてみたものの、特に影響しそうな変更等は見当りません。いよいよ万策尽きたか……と思いかけたころ、⁠じゃ、gnutlsを削除したらどうなるの?」というアイデアが閃きました。

gnutlsはTLS(Transport Layer Security)の名が示すように、通信経路を暗号化してセキュリティを高めるためのライブラリなので、通信機能を持つ多数のコマンドが依存しており、削除するとネットワーク回りはほぼ使えなくなります。しかしながら、mate-calcはgnutlsには依存していないので削除しても動くはずです。そこで一時的にgnutlsを削除して、mate-calcがどうなるか試してみました。

$ sudo removepkg gnutls
[sudo] kojima のパスワード:

Removing package gnutls...
Removing files:
  --> Deleting symlink usr/lib/libgnutls.so
  --> Deleting symlink usr/lib/libgnutls.so.30
...

$ mate-calc
libgnutls.so.30: 共有オブジェクトファイルを開けません: そのようなファイルやディレクトリはありません
Failed to load module: /usr/lib/gio/modules/libgiognutls.so

ビンゴ!です。mate-calcだけではgnutlsとの関連は見つからなかったものの、mate-calcが利用するglibがGIOの下請けモジュールのlibgiognutls.so経由でgnutlsを利用していました。このlibgiognutls.soの出自を調べると、glib_networkingパッケージ由来でバージョンは2.54.1でした。

$ grep libgiognutls.so /var/log/packages/*
/var/log/packages/glib_networking:usr/lib/gio/modules/libgiognutls.so
$ head /var/log/packages/glib_networking
 PACKAGE NAME:     glib_networking-2.54.1-x86_64-B2
 COMPRESSED PACKAGE SIZE:     83 K
 UNCOMPRESSED PACKAGE SIZE:     230 K
 PACKAGE LOCATION: /var/adm/mount/plamo/05_ext/glib_networking-2.54.1-x86_64-B2.txz
 PACKAGE DESCRIPTION:
 glib_networking: Glib用ネットワーク関連モジュール
 glib_networking:   
 glib_networking: Glibにネットワーク関連の機能を追加するためのgiomodule群
 ...

一方、glib-networkingの開発元のサイトを確認すると、最新版はバージョン2.72.2まで進化しています。そこでこの最新版をビルド、パッケージ化して更新したところ、gnutls-3.7.7でも無事mate-calc-1.26が動作するようになりました。

glibはGNOME系のソフトウェアが利用している汎用ライブラリで、メモリ管理や文字列操作、ファイル入出力等の機能を抽象化して、CPUの種類やOSに関わらず、同じ手順で利用できるようにします。GIO(Glib I/O)はこのglibが利用する入出力ライブラリ群で、ローカルファイルとネットワーク上のURLを同等に扱うための機能を提供します。

mate-calcがglibを必要とするのは"readelf -d"等で必要な共有ライブラリを調べた際にわかっていました。一方glibでは、入出力回りを扱うGIO関連の機能はモジュールとして必要に応じて動的にロードする設計になっており、その結果、glibが依存するライブラリを調べるだけではgnutlsとの関連がわからなかったのでした。

というわけで、⁠風が吹けば桶屋が儲かる」ならぬ「gnutlsをバージョンアップするとmate-calcがSeg.Faultする」という問題は、両者を介在しつつも直接は目に触れないglib-networkingの"libgiognutls.so"モジュールが関わっていた、ということに落ちつきました。


「gnutlsを更新するとmate-calcがSeg.Faultする」という問題は何とか解決できたものの、⁠電卓」ソフトウェアであるmate-calcがなぜglib/GIO経由でネットワーク機能を利用しようとするのか、というあたりはよくわかりませんでした。

そこで本稿を執筆するにあたり、改めてmate-calcのソースコードを調べてみたところ、mate-calcの「財務」モードには「円/$」のような通貨間の換算レートを表示する機能があり、その機能のためにIMFのホームページから最新の為替レートを入手するようになっていて、その際にglib-networkingを利用していることが確認できました。

「風が吹けば…」ほどの複雑な連鎖ではないものの、⁠mate-calcはIMFの為替レートを入手するためにglibを使う、glibはGIO/glib-networkingで指定されたURLを参照し、その際にlibgiognutls.soモジュールを使う、ところがこのモジュールは数年前のバージョンのままだったので、最新のgnutlsとはABI的なミスマッチがある、よってgnutlsを最新にするとmate-calcがSeg.Faultする。この問題はglib-networkingのバージョン依存なので、最新のgnutls環境でmate-calcを再ビルドしてもダメで、glib-networkingを更新しないと解決しない」という因果関係が判明し、その夜はすっきりした気持で祝杯を傾けることができました。

おすすめ記事

記事・ニュース一覧