HN 日本語サマリー

← 一覧へ戻る
プログラミング

Solo – 静的Linuxバイナリ用の.soローダー

Solo – a .so loader for static Linux binaries (github.com)

178 pointsby zX41ZdbW205 コメント

要約

Soloは、静的にリンクされたmuslバイナリが実行時にホストのglibcリンク済み共有ライブラリ(GPUドライバなど)をロードできるようにするツールです。これにより、コンテナや追加のlibcなしで、単一の実行可能ファイルとしてGPU機能を利用する静的バイナリのデプロイが可能になります。このツールは、glibc ABIブリッジを実装し、x86-64およびaarch64アーキテクチャで動作します。

全文翻訳

Solo — 静的Linuxバイナリ用の.soローダー 単一のmuslリンク済み実行可能ファイルを配布します。実行時に、ユーザーが既に持つglibcリンク済みのGPUドライバをロードします。コンテナもAppImageも、プロセス内に2つ目のlibcも不要です。 静的バイナリは、Linuxでソフトウェアを展開するための、驚くほど退屈な方法です。1つのファイル、依存関係なし、壊れるものなし。私たちはIX、つまり完全に静的なLinuxバイナリを生成するためのソースファーストのビルドシステムでそれらをビルドします。 アプリケーションがGPUを必要とする瞬間、その退屈さは終わります。VulkanとOpenGLドライバはホストから共有オブジェクトとして提供されますが、通常はglibcに対してビルドされており、完全に静的なmuslバイナリは通常それらをdlopen()できません。Soloはその境界を越えます。それは、独自のELFローダー(x86-64およびaarch64)と、musl上で実装されたglibc ABIブリッジに裏打ちされたdlfcnスタイルのソースAPIを提供します。結果は依然として単一の通常の静的実行可能ファイルですが、マシンに既にインストールされているグラフィックスドライバを使用できます。 リポジトリには、エンドツーエンドのVulkanプルーフが含まれています。完全に静的な実行可能ファイルが、ホストの変更されていないVulkanドライバをロードし、コンピュートシェーダーを実行し、結果をPNGに書き込みます。Linux上のAMD radv、radeonsi、Intel、NVIDIA GPU、およびAsahi Linux上のApple M1でテスト済みです。ホストはハードウェア固有のコードを保持します。あなたはそれ以外のすべてを配布します。 デモの言葉だけではありません。コミットごとに、CIはx86-64とaarch64の両方で、SoLoを介して、最もインストールされているDebianパッケージ1,000個(2,100以上のホストオブジェクト)の共有ライブラリをロードします。 動作を確認する プリビルドバイナリを入手してください — クローン不要、ツールチェーン不要、Vulkanドライバがインストールされている任意のLinux(mesa-vulkan-driversで十分): curl -LO https://github.com/pg83/solo/releases/latest/download/vulkan-x86_64 chmod +x vulkan-x86_64 ./vulkan-x86_64 hello.png vulkan-aarch64は、arm64マシン向けの同じデモです。コマンドは通常の方法でディストリビューションにインストールされたVulkan ICDを検出し、512×512のRGBA画像を生成します。 これがShittyリリースバイナリのビルド方法です — ところで、非常に高速なターミナルエミュレータです! 特定のドライバを強制するには: ./vulkan-x86_64 --driver /usr/share/vulkan/icd.d/radeon_icd.x86_64.json radeon.png ./vulkan-x86_64 --driver /usr/share/vulkan/icd.d/lvp_icd.json lavapipe.png ICDマニフェスト名はディストリビューションによって若干異なります。--driverを指定しない場合、組み込みのKhronosローダーが通常の検出を実行します。 実行可能ファイル自体が動的にリンクされていないことを確認できます: readelf -lW ./vulkan-x86_64 | grep INTERP # 出力なし readelf -dW ./vulkan-x86_64 # "There is no dynamic section" または、Python 3とPATHにあるC/C++コンパイラを使用して、同じデモをソースからビルドします: git clone https://github.com/pg83/solo.git cd solo ./build vulkan ./vulkan hello.png これはvkCreateInstanceの単なる呼び出しではありません。デモは以下の通りです。 静的にリンクされたKhronos Vulkanローダーに入る。 SoLoを介してホストのVulkan ICDとその非glibc依存関係をロードする。 デバイス、ストレージバッファ、ディスクリプタセット、コンピュートパイプラインを作成する。 チェックインされたSPIR-Vシェーダーをディスパッチする。 結果をマップし、静的にリンクされたlibpngを介して書き込む。 完全な例はbin/vulkanにあり、Vulkanプログラム自体はmain.cppにあります。 仕組み ┌──────────────────── 静的にリンクされた実行可能ファイル ────────────────────┐ │ │ │ アプリケーション → 組み込みVulkanローダー → SoLo dlopen/dlsym │ │ ├─ x86-64 ELFマッパー │ │ └─ glibc ABI → musl │ │ │ │ └───────────────────────────────────────────┬─────────────────────┘ │ 実行時にマップ ▼ システム Mesa/Vulkan ICD.so + DSOs └───────────────────────────────────────────┘ elf_loader.cpp はELFセグメントをマップし、DT_NEEDEDをウォークし、バージョン付きシンボルを解決し、x86-64リロケーションを適用し、ELF TLSおよびTLSDESCをサポートし、IFUNCを具体化し、RELROを適用し、初期化を実行します。それ自体がELF DSOである依存関係は再帰的にロードされます。glibcは意図的にロードされません。malloc@GLIBC_2.2.5のようなインポートは、glibc_shim.cppによって、プロセスに既存のmuslランタイム上のABI互換アダプターに解決されます。サポートされていないglibc関数には、サイレントにプロセスを破損させるのではなく、呼び出された場合に正確なシンボルとバージョンで大声で失敗する、ユニークに生成されたスタブがあります。 muslは各アーキテクチャのglibc ABIに合わせて同期オブジェクトのサイズを決定するため、ブリッジはそれらをシャドウしません。ドライバが作成したpthread_mutex_tはそのまま使用されます。したがって、ロックはロードされたDSOとそれを共有する可能性のある静的実行可能ファイルの両方に対して1つのロックであり、glibcの静的な再帰的およびエラーチェック初期化子は初回使用時に採用されます。 ディスクからDSOをロードする前に、SoLoは静的プロバイダーレジストリをチェックします。これにより、アプリケーションはWaylandなどの依存関係を、既に実行可能ファイルにリンクされている関数で満たすことができます。LD_LIBRARY_PATHおよびDL_ELF_LIBRARY_PATHは、標準システムディレクトリ外のライブラリに対して尊重されます。 興味深い部分は読み取るのに十分小さいです。 lib/dlfcn.cpp — dlopen、dlsym、エラー、および静的プロバイダー lib/elf_loader.cpp — ELFマッピング、シンボル、リロケーション、およびTLS lib/glibc_shim.cpp — 実装されたglibc ABIアダプター lib/glibc_stubs.cpp — 残りのABIの明示的なフォールバック ライブラリとして使用する デフォルトのターゲットはスタンドアロンアーカイブをビルドします: ./build 公開されている./dlfcnシンボリックリンクは、結果のlibdlfcn.aを指します。lib/dlfcn.hをインクルードし、アーカイブをmusl-staticアプリケーションにリンクすると、通常のdlopen()/dlsym()呼び出しはSoLoにリダイレクトされます。ソースツリーは意図的に自己完結しており、別の静的ビルドグラフにコピーするのに適しています。 実験を再現する ./build test # スモークテストでArch glibc DSOクロージャをロードする ./build vulkan_test # 静的デモをビルドし、ネイティブLavapipe PNGを検証する CIは、GCCを使用したAlpine/musl、GCCを使用したFedora、およびClangを使用したUbuntuでネイティブビルドとテストを実行します。Vulkanテストは、各ディストリビューション固有のLavapipeパッケージをインストールします。Arch sysrootからドライバを実行しません。スタンドアロンVulkan実行可能ファイルのすべてのビルド入力は、bin/vulkanの下にベンダーされています。 build.py はそれらのソースを直接コンパイルします。アップストリームのCMake、Meson、configure、およびMakeビルドシステムは呼び出されません。 ベンダーされたバージョン musl 1.2.5 (0784374d561435f7c787a555aeab8ede699ed298) LLVMランタイム 15.0.7: libc++、libc++abi、libunwind、およびcompiler-rt組み込み (8dfdcc7b7bf66834a761bd8de445840ef68e4d1a) Vulkan Headers 1.4.357 (e3b1eec08173d6b825cd3ac88c885a63b621504a) Vulkan Loader 1.4.357 (5f157b62e333c63260d05d81bf66faa216ab0fb8) zlib 1.3.2 (da607da739fa6047df13e66a2af6b8bec7c2a498) libpng 1.6.50 (2b978915d82377df13fcbb1fb56660195ded868a) ライセンスファイルは対応するソースの隣に保持されています。shader.inc は shader.comp のチェックインされたSPIR-V形式であるため、シェーダーコンパイラは不要です。 先行研究との違い 一般的なケースでは、SoLoのみが静的アプリケーションに「このシステムDSOのlibwayland依存関係について、私の実行可能ファイルに既にリンクされているシンボルを使用する」と動的ローダーに伝えることができます。これにより、アプリケーションはサポートされているすべてのシステムで利用可能な最も古いバージョンをターゲットにするのではなく、最新のlibwaylandを埋め込むことができます。そして、2つの世界の境界は薄いdlsymシムではありません。それは、外国のコードを実際に動作させる部分を運びます。C++例外は両方向にそれを横断します。静的世界でのスローはglibcコンパイル済みフレームを介してアンワインドされ、glibcキャッチに入り、その逆も同様です。デストラクタは両側で実行されます。ゲストの_Unwind_*インポートは、実行可能ファイル内の1つのアンワインダーにバインドされているため、プロセス内に2つの戦うものがあるのではなく、単一の例外機構があります。ラッパーやコードパッチなしで、すべての4つのTLSモデル。一般およびローカル動的(__tls_get_addr経由)、TLSDESC(カスタムABIリゾルバー経由)、および初期実行(ローダーが傍受できないプレーンなスレッドポインター相対オフセットのGOTスロット)は、実行可能ファイル自体の静的TLSに乗る余剰アリーナから提供されるため、プロセス全体で有効な1つのオフセットがすべてのスレッドで有効になり、変更されていないmuslがスレッドごとのレイアウトを行います。ld.soのバインディングセマンティクスであり、近似ではありません。グローバルスコープのインターポジション、RTLD_DEEPBIND、DT_SYMBOLIC、バージョンなしプロバイダーの互換性ルールを持つシンボルバージョニング、引数レジスタがtを保持したままの遅延PLTバインディング