- Wine は POSIX 互換 OS(Linux、macOS、BSD)で Windows プログラムを実行可能にする互換レイヤー
- Valve の Steam Deck も Wine ベースのソリューションを使用
WINE = Wine Is Not an Emulator
- エミュレーター方式は遅く、実際には Linux/macOS は Windows バイナリをネイティブに実行できる(少し手助けすれば)
- デバッガーを通して Linux/Windows バイナリがどのように動作するかを詳しく説明
Hello, Wine!
- 基本的に Wine は Windows 実行ファイルに対する「Dynamic Loader」
- ネイティブの Linux バイナリであり、EXE や DLL をどう処理すべきかを理解している
- Wine は Windows 実行ファイルをメモリに読み込んだ後、解析して依存関係を把握し、実行すべきコードへジャンプする
- これだけでもひとまず Windows バイナリを実行可能だが、例外がある
System Calls
- syscalls と呼ばれるシステムコールが Wine を複雑にしている
- syscalls は OS に実装されるものであり、実行ファイルやライブラリに入っているものではない
- OS が提供する syscalls は OS API
- Linux : read, write, open, brk, getpid,..
- Windows : NtReadFile, NtCreateProcess, NtCreateMutant,..
- システムコールはコード内の通常の関数呼び出しとは異なる。たとえばファイルを開く処理は File Descriptor を追跡するため、カーネルで処理されなければならない
- したがってアプリケーションコードには、自身で「割り込み」をかけてカーネルに制御を渡す方法が必要になる(「Context Switch」)
- OS が提供する関数群と呼び出し方法は OS ごとに異なる
- Linux では read() 関数を呼ぶとき、ファイルディスクリプタをレジスタ
%rdi に、バッファポインタを %rsi に、読み込むバイト数を %rdx に入れなければならない
- しかし Windows には read() 関数がカーネル内にない
- 同じ「Hello World!」を表示するコードを Linux/Windows で実行してみると
- Linux は libc.so の puts を、Windows は ucrtbase.dll の printf を呼び出す
- 最近の Linux では静的リンクして puts 実装をバイナリ内に含めてしまい、libc.so が実行時に使われないのが一般的
- Windows では少なくとも最近までは「システムコールを直接使うのはマルウェアだけだった」
- 一般的なアプリケーションは常に kernel32.dll/kernelbase.dll/ntdll.dll に依存し、カーネルと直接通信しないようになっている
Runtime translation of Syscalls
- syscall をインターセプトしたらどうだろう?
アプリケーションが NtWriteFile() を呼ぶときに割り込み、write() を呼んだあとバイナリが望む結果フォーマットで返してやれば?
- カスタム版の ucrtbase.dll を提供すれば可能だろうが、複雑な問題が発生する
- その代わり、バイナリとカーネルの間に挟まっている ntdll.dll を修正する
- 最近の Wine は ntdll.dll(PE バイナリ)と ntdll.so(ELF バイナリ)で構成される
- dll は薄いレイヤーで、単に呼び出しを ELF 側へリダイレクトする
- ELF には
__wine_syscall_dispatcher という特別な関数があり、現在のスタックを Windows から Linux、またはその逆へ変換する魔法を行う
- この syscall dispatcher が Windows の世界と Linux の世界をつなぐ橋になる
- 呼び出し規約を処理し、スタック領域を確保し、レジスタを移し替えるといった処理を行う
- 実行が ntdll.so に来て Linux バイナリ側へ渡ると、こちらはすべての Linux API を使えるようになる
これで全部?
- とても簡単そうに聞こえるが…
- Windows API は非常に多く、文書化も不十分で、既知・未知のバグがあり、それらをそのまま保存しなければならない。Wine のコードの大半は、さまざまな Windows DLL の実装になっている
- syscall の呼び出しにはさまざまな方法があり、技術的にはアプリケーションが syscall を直接呼ぶことを防ぐ方法がない
(Windows ゲームは何でもやることを覚えておこう)
Linux カーネルにはこれを処理する特別なメカニズムがあり、もちろんこれが複雑さを増している
- 32bit vs 64bit の問題もナンセンスだ。膨大な数の 32 ビットゲームがあり、それらが再び 64 ビットで再リリースされることはない。Wine は両方をサポートするため、これも複雑さを増している
- ここでは wine-server についてはまだ触れていない。これは Wine が生成する別プロセスで、カーネルの「状態」(ファイルディスクリプタ、ミューテックスなど)を維持する
- ゲームを動かしたいなら? DirectX、PulseAudio、入力デバイスなどを処理しなければならず、やることは非常に多い
- Wine は長年開発されてきて、大きく前進した。今日では Cyberpunk 2077 や Elden Ring のような最新ゲームも問題なく動かせる
ときには Wine が Windows より優れた性能を見せることさえある
11件のコメント
内容も内容ですが、要約のクオリティが本当にものすごいですね。ありがとうございます。
サブスクリプション型の読書サービスを提供する yes24 と教保文庫を利用しています。
自宅PCの環境をUbuntuに変えてから、Wineを使って YES24 と教保文庫を実行してみました。
どちらも別個のDRMを使っているので動くのかと思いましたが、Qtで作られていると認識している YES24 は問題なく動作し、教保文庫 EBOOK は動きませんでした。(UIは起動、DRMは起動せず)
どちらもDRMが適用されていると思うのですが、何が違って動いたり動かなかったりするのか考えてみたものの、上の記事を読むとだいたい理解できそうですね(「だいたい完全に理解した」画像)
wine5.0以降、カカオトークが何の設定もなしに実行できるようになってうれしいです。(カカオトークを使いたいかどうかとは別として……)
画面表示に多少問題はありますが、クリップボード画像の送信などの機能はシームレスに動作します。
全体としてWineはゲーム実行に注力しているように見えますが、さまざまなアプリもきちんと動かしてくれるので良いですね。
公共機関へのLinux導入の話が出ても、Linux版カカオトークは考えもしないのはちょっと……かなり残念です。
Mac版はあっさり作ったのに……
大まかなことは知っていましたが、こんなに詳しくWineを説明してくれるとは不思議ですね.. w
概してWineはWindowsプログラムをうまく実行してくれるので、Wineを使ってクロスプラットフォームアプリを作る、という構想も可能でしょうか?(デスクトップ限定)
かなり昔には、ハンコムのHWPもWineベースでLinux向けに移植されて発売されたことがあったと記憶しています。(R4には別個のwin32互換ライブラリレイヤーが入っていて、Wineが使われたのはR5だったか2002だったか、ちょっとあやふやですが)
そのため、一時期はWineのおかげでwin32が最も大衆的で成功したクロスプラットフォームAPIだ、というジョークもありました。
でも今はelectron/wasmの時代ですね;;
少し話は変わりますが、もしそうするのであれば、Wine のライセンスは LGPL であるため、コードの書き方によってはソースコードの一部または全部を公開しなければならない場合があります。
原文でも説明されていますが、Wine がエミュレーターではない理由は、CPU 命令をそのまま使うからです。つまり、Wine で動かせるソフトウェアは基本的に x86 または x86-64 CPU 上で動作する Windows 向けソフトウェアだという意味です。
Apple は Mac 全体を ARM アーキテクチャへ移行したうえ、MS でも ARM ベースの開発キット を出している現時点では、x86(-64) ベースの CPU でしか動作しないソフトウェアを「クロスプラットフォーム対応」と呼ぶのは、やや無理があるのではないかと思います。
はい。おっしゃる通り……私も気づかないうちにx86系マシンに限定してしまっていたようです。
ElectronやTauriがあるので、クロスプラットフォームを最初から作らなければならないなら、あまり良い選択ではないように思います。 Webブラウザベースの技術を使えない特別な制約があるなら、 クロスコンパイルをしっかりサポートしているQtのようなライブラリを使うほうが良いかもしれませんね..
222