先月くらいまでは対岸の火事のように思っていた新型コロナウィルスが、あれよあれよと言う間に世界中に蔓延し、「日常」を大きく変えてしまいました。今や感染の中心は欧米に移動し、ローマやパリ、ニューヨークといった観光名所から人々の姿が消えた映像は、私たちの「あたりまえ」がいかに脆弱な足場の上に築かれていたかを実感させます。
さて、今回はWINEのトラブルネタの最終回として、ささいなミスで1ヵ月以上迷宮をさまよった記録になります。
8月ZX日:ビルドしたWINEにトラブル発覚
ビルドしたWINEパッケージをインストールし、Windows用のソフトウェアをあれこれ試しているうちに奇妙なトラブルに気づいた。というのは、仮想環境(VirtualBox:以下VBoxと略)と実機で動作が異なるソフトがあることだ。
たとえば123 Free Solitaireというカードゲームは、効果音も含めてVBox上でも実機上でも完全に動く。
図1 実機上の「123 Free Solitare」
一方、弾幕シューティングとして有名な上海アリス幻樂団の東方プロジェクトシリーズの体験版は、VBox上では起動するものの、実機ではオープニング画面にすら辿りつかずにエラー終了してしまう。
図2 VBoxでは起動する(上)ソフトが実機(下)ではエラーになる
もっとも、VBoxでは「起動する」とは言うものの動作はすさまじく遅く、タイトル画面が表示されるまでに数分かかり、キー入力はほとんど不可能で、ゲームとしてはとてもプレイできないレベルだ。
実機上でコマンドラインから起動してみると、エラーメッセージとともにエラーのBacktraceも表示されるものの、"wined3d"あたりでエラーになっていることくらいしかわからない。
$ wine .wine/drive_c/Program\ Files\ \(x86\)/上海アリス幻樂団/東方紺珠伝体験版/th15.exe
0009:fixme:ver:GetCurrentPackageId (0x32ed60 (nil)): stub
0009:fixme:dwmapi:DwmEnableComposition (0) stub
wine: Unhandled page fault on execute access to 00000000 at address 00000000 (thread 002c), starting debugger...
0033:fixme:dbghelp:elf_search_auxv can't find symbol in module
...
Unhandled exception: page fault on execute access to 0x00000000 in 32-bit code (0x00000000).
0033:fixme:dbghelp:elf_search_auxv can't find symbol in module
Register dump:
CS:0023 SS:002b DS:002b ES:002b FS:0063 GS:006b
EIP:00000000 ESP:00d8fdbc EBP:00d8fdd8 EFLAGS:00010246( R- -- I Z- -P- )
EAX:05600076 EBX:00000000 ECX:00000003 EDX:00000001
ESI:00195e30 EDI:00000001
Stack dump:
0x00d8fdbc: 7d522771 00000001 00000000 00d8fdf8
0x00d8fdcc: 7d5246d3 001e6b90 00195e30 00d8fdf8
0x00d8fddc: 7d5246e0 6e78a350 000000bf 00000500
0x00d8fdec: 001e5658 00195e30 001e5f50 00d8fec8
0x00d8fdfc: 7df3588e 00000001 001e5d48 00000000
0x00d8fe0c: 7decf8fa 00000000 000000bf 00000001
Backtrace:
=>0 0x00000000 (0x00d8fdd8)
1 0x7d5246e0 X11DRV_wglSwapIntervalEXT+0x6f() in winex11 (0x00d8fdf8)
2 0x7df3588e swapchain_gl_present+0xdd() in wined3d (0x00d8fec8)
3 0x7dec9f1c wined3d_cs_exec_present+0x2b() in wined3d (0x00d8fef8)
4 0x7decb236 wined3d_cs_run+0x115() in wined3d (0x00d8ff48)
5 0x7bcac9cc call_thread_func_wrapper+0xb() in ntdll (0x00d8ff5c)
6 0x7bcaff07 call_thread_func+0x86() in ntdll (0x00d8ffdc)
7 0x7bcac9be call_thread_entry+0x9() in ntdll (0x00d8ffec)
0x00000000: -- no code accessible --
Modules:
Module Address Debug info Name (133 modules)
PE 400000- 522000 Deferred th15
ELF 6d3ca000-6d500000 Deferred oleaut32
....
他のWindows用ソフトはどうだろう…… とあれこれ試してみた結果、グラフィックを多用しないロールプレイゲームとかは動作するものの、シューティング等のアクションゲームは同じエラーになる。
どうして動くものと動かないものがあるのだろう、と調べていった結果、どうやらWindowsのDirectX機能を使っているソフトウェアが実機ではエラーになるようだ。
このあたりは不勉強だったので、そもそもDirectXとは何か、あたりから調べてみたところ、MicrosoftがWindows用に開発したゲームやマルチメディア用のAPIで、ビデオカードの3Dレンダリング機能などを容易に操作できるようになっているため、3DのシューティングゲームなどはたいていこのAPIを使っているらしい。
一方、WINEが動くLinuxの世界ではOpenGLが3Dレンダリングの標準的なAPIなので、WINEはDirectX用のコマンドをOpenGL用に翻訳して使っているそうだ。また、DirectXにはさまざまなバージョンが存在し、それに対応するためWINEにも"d3dXX"と呼ばれるライブラリが複数用意されている、などがわかってきた。
Windowsの世界ではDirectXの動作診断をするためのdxdiagというツールがあり、このツールで「Direct3Dのテスト」を試すと、VirtualBox上ではきちんと"DirectX"のロゴが付いた立方体が回転するのに対し、実機では画面が640x480に変ってWINEがエラー終了することがわかった。どうやらこのコマンドがDirectXの可否のテストに使えそうだ。
図3 VBoxでは起動する(上)ソフトが実機(下)ではエラーになる
たぶん原因はDirectX回りだろう、と絞れてはきたものの、果してどこから手を付けたものだろう。
9月XX日:苦闘
WINEにはより詳細なログを取るためのWINEDEBUGという環境変数が用意されているものの、dxdiagがエラー終了するまでに30万行ほどのログを出力するのでとても調べきれない。また、エラーメッセージを手掛りにあれこれググってみても、似たような症状は出てこない。
そもそもWINEが全然動かないわけではなく、同じバイナリがVBox上では動いているのだから、問題はWINEではなく、実行環境側にありそうだ。
VBoxと実機の違いで最初に思いつくのはビデオカード回りで、エラーになる実機はGPU内蔵のRyzen5を積んでいる。それではIntelなCPUではどうだろう、と考えて、同じくGPU内蔵タイプのCore i3で試しても同じエラーで終了する。どうやらVBoxのビデオドライバのみが遅いながらも機能するらしい。
OpenGLが絡むし、機種ごとに違いがあるようなので、各ビデオカード用にDRIのライブラリを用意しているMesaあたりが原因だろうか…… とMesaのビルドオプションをあれこれイジってビルドし直してみても変化なし。MesaのドライバはLLVMを使っていたなぁ、、とLLVMに遡って再ビルドしても変化なし。MesaをイジればXのドライバも影響を受けるかも、とXのライブラリやドライバ類を再ビルドしてもダメ。いずれの修正も64ビット版、32ビット版それぞれに反映させなければならないので、ビルドの手間は2倍かかる。
MesaもWINEも更新頻度は高いので、しばらく悩んでいるうちに新しいバージョンがリリースされる。ほのかな期待を持って新バージョンを試してみても、やはりダメ。
1ヵ月近くあれこれ試しても解決できなかったので、当初の目的だったWINE対応は諦めて、CLFSレベルのMultilib環境ということでお茶を濁すかなぁ……、と考えがちになってきた。
9月XY日:光明
今回のトラブルが何故ここまで厄介なのかを考えると、WINEを動かすためには150を超える32ビット版のパッケージが必要で、たぶん、その中に問題を起しているパッケージがあるものの、150のパッケージは相互に関連していて、一部だけを切り分けて試すことができないからだ。
トラブル解決の基本は「条件を絞りこんで問題を切り分けること」なので、今回の場合、どうすれば問題を切り分けるだろうか、と考えていくうち、Plamo以外のディストリビューションのWINE用のパッケージを流用すれば何とかなるのでは、と思いついた。
そのためには、あるディストリビューションでWINEがちゃんと動き、WINEを動かすためにはどのようなパッケージが必要になるかを確認する必要がある。通常、他のディストリビューションの動作テストはVBox上でやるのだけど、今回はハードウェア環境も影響しているので実機上で試す必要がある。そこでまずハードディスクを整理して専用のパーティションを用意し、そこにUbuntuを入れてみた。
Ubuntu上で"sudo apt install wine64"を実行すると、多数のパッケージが芋ヅル式にインストールされていく。一通りインストールと設定が終って"wine dxdiag" を実行すると、フルスクリーン上でDirectXキューブが問題なく回転した。
それでは「UbuntuのWINE関連パッケージと手元でビルドしたパッケージの違いは……」と調べようとしたものの、UbuntuのようなDebian系のディストリビューションではパッケージの分け方がPlamoとは大きく異なるので簡単に比較できそうにない。
次に目を付けたのがArch Linuxで、こちらも同様にHDD上に入れた後、"pacman"を使ってWINEをインストールし、dxdiagも動作することを確認した。
Arch LinuxのパッケージはPlamoとよく似た作りになっていてtarコマンドだけで展開できる。そこで、Arch LinuxのWINE関連パッケージをPlamo環境に持ち込み、既存のファイルに上書きする形でインストールしてみたところ、Plamo環境でもdxdiagがきちんと実行できた。
Arch Linuxのバイナリを使えばPlamo上でもdxdiagが動作するなら、後はどのパッケージが原因でクラッシュするのかを調べる作業になる。そこで手元で作ったPlamo用のパッケージを一つずつ書き戻しながらdxdiagの動作を調べると、libglvndというライブラリの32ビット版をインストールするとクラッシュすることが判明した。
libglvndというライブラリは、WINE用のパッケージを作成している時に見つけたソフトウェアで、OpenGLのベンダー依存APIへの呼び出しを適切なライブラリへ振り分ける機能を提供しているらしい。
確かにlibglvndはMesaのビルド時にもチェックされるOpenGLの要(かなめ)のひとつではあるものの、小さなライブラリでビルド時に指定できるオプションもそれほど多くはない。一体何が違っているのだろう、とArch LinuxのPKGBUILDスクリプトを調べたところ、32ビット版のconfigure時に"--build=i686-unknown-linux-gnu"を渡している。一方、Plamoのビルドスクリプトでは"--host=x86-pc-linux"を指定していた。
そう言えば、libglvndは一部アセンブラのコードが入っているようで、"gcc -m32"だけではビルドできなかったから"--host=..."で機種を明示したのだっけ、と思い出し、改めて"--build=i686-unknown-linux-gnu"の指定でビルドし直すと、手元で作成したlibglvndでもdxdiagが正しく動作し、PlamoでもWINEが動作する、と言えるようになった。
図4 実機でもDirect3Dのテストが動いた
改めて"--host=x86-pc-linux"と"--build=i686-unknown-linux-gnu"の指定で何が違ってくるのかを調べたところ、i686-unknown-linux-gnuの指定ではi686、すなわちx86系の第6世代CPUと認識し、それらが持っている拡張機能(MMX、3DNOW, SSE等)を使うようにビルドされるのに対し、x86-pc-linux-gnuの指定ではより汎用的なx86(=80386)と判断され、i686用にアセンブラで書かれた拡張機能が利用できなくなることが原因のようだ。
-- build/Makefile 2020-01-22 08:47:18.568112237 +0900
+++ build_ng/Makefile 2020-01-22 08:46:40.478973090 +0900
@@ -86,9 +86,9 @@ POST_INSTALL = :
NORMAL_UNINSTALL = :
PRE_UNINSTALL = :
POST_UNINSTALL = :
-build_triplet = i686-pc-linux-gnu
-host_triplet = i686-pc-linux-gnu
-target_triplet = i686-pc-linux-gnu
+build_triplet = x86_64-pc-linux-gnu
+host_triplet = x86-pc-linux-gnu
+target_triplet = x86-pc-linux-gnu
...
-CFLAGS = -Wall -Werror -include config.h -fvisibility=hidden -DUSE_X86_ASM -DUSE_MMX_ASM
-DUSE_3DNOW_ASM -DUSE_SSE_ASM
+CFLAGS = -Wall -Werror -include config.h -fvisibility=hidden
CPP = gcc -m32 -E
ビルド時にi686とx86を間違えた1つのパッケージのせいでシステム全体が動かなくなる、ディストリビューションを作っているとこの手のトラブルにはちょくちょく遭遇するものの、今回はそれが150余のパッケージの中に紛れこんでいたため、検出にずいぶん時間がかかってしまった。
「急がば回れ」ではないけれど、面倒な仕事ほど、それぞれの作業に細心の注意が必要なことを肝に銘じることにしよう。
このシリーズで取りあげたWINEの作成過程で一番キツかったのが、今回取りあげたDirectXの動作不能問題です。
全体像が見えない中で32ビット版のライブラリを作り続けるのも大変でしたが、こちらはキリのいいところで実行するconfigureの結果で進捗がわかったので、少しずつでも目的地に近づいていることを感じられたのに対し、DirectXの動作不能問題は出口があるのかすらわからない迷宮の中を延々と手探りでさまよっているような絶望感に苛(さいな)まれる日々でした。
その分、出口らしい明りが見え、つまづきながらもそちらに辿りつき、脱出できた時の爽快感は、まさに「エウレカ!」と叫びたくなるものでした。筆者にとっては、この「エウレカ」体験で得られる爽快感が、ディストリビューションを作り続ける原動力になっているようです。
さて、ビルド時の苦労話が一段落したので、次回はWINEの設定や遊び方を紹介しましょう。