採用情報

お問い合わせ

BLOG

Linux の知識・学習 BLOG

Linux の知識・学習 BLOG

2024 年 02 月 20 日

ABI 互換性: ABI(Application Binary Interface) とは?、変わらないこと、互換であることで AlmaLinux で可能になること

2023 年 6 月、AlmaLinux OS Foundation の委員会は RHEL(Red Hat Enterprise Linux) との 1:1(1 対 1) の互換対応をやめ、代わりにバイナリ互換 (ABI(Application Binary Interface)-compatible) であることに狙いを変更することを発表しました。※1
この発表が意味するところは OS の低レイヤー部分の仕組みについて詳しくない方は意味を掴みにくいかもしれません。
また、リリースするパッケージを 1:1 で対応させるのではなく、ABI 互換は維持してリリースするという方針についても解説なしに理解することはわからないかもしれません。

この記事ではバイナリ /ABI 互換の意味する技術的な側面と周辺情報について紹介をします。AlmaLinux 固有の情報ではなく、他の Linux ディストリビューションを含めた一般論や他の系列の OS も含めた横断的な情報を取り扱います。

※1
the AlmaLinux OS Foundation board today has decided to drop the aim to be 1:1 with RHEL. AlmaLinux OS will instead aim to be binary compatible with RHEL*.
* Binary/ABI compatibility in our case means working to ensure that applications built to run on RHEL (or RHEL clones) can run without issue on AlmaLinux. Adjusting to this expectation removes our need to ensure that everything we release is an exact copy of the source code that you would get with RHEL. This includes kernel compatibility and application compatibility.
https://almalinux.org/blog/future-of-almalinux/

ABI とは? なぜ使用する OS によって ABI は異なるのか?

Application Binary Interface とは OS( オペレーティングシステム ) の低レイヤに関する技術用語です、狭義には OS とアプリケーションの間の規約を指します、この規約というのは例えばシステム・コールやその呼び出し方法、データの形式やサイズなどのことです。※2ABI は通常 OS やその OS が動作する CPU アーキテクチャによって異なります。例えば 同じ Linux カーネルをベースにした、いわゆる Linux ディストリビューションでも amd64(x86_64) と arm64(aarch64)、powerpc とさまざまな CPU アーキテクチャ向けにソフトウェアがビルドされます。一般にアーキテクチャが異なればコンパイルされたオブジェクトコード ※3 は他のプラットフォーム向けには互換性を持たないため動作しません。同様に異なる OS に対してもオブジェクトコードは互換性を持ちません。※4 なぜならシステム・コールの呼び出し規約やデータ構造は OS によって異なるためです。
また Linux ディストリビューションによっても、ディストリビューションに含む共有ライブラリのバージョンが通例的には異なるため、ディストリビューション間での共有ライブラリの ABI 互換は保証されません。
特別な配慮 ( すべてのライブラリを静的リンクするなど ) などを行わない限り、例えば RHEL 向けにビルドした実行コードは Ubuntu では動かないとみなすべきですし、安全のためにはディストリビューションごとに個別にビルドするのが望ましいでしょう。

ABI 互換性がある、と述べられた場合、ある OS/ アーキテクチャ向けにビルドされたオブジェクトコードがそのシステムでも動作することを指し、互換性が壊されないように注意深く構築されているという意味合いを持ちます。

※2
カーネルモジュール向けのインターフェースも含みますが、この場合 'kABI' と呼んで、区別されることが多いです。また Linux カーネルのコミュニティでは、ユーザーランド ( 通常のアプリケーションが動作する環境 ) を壊さないことは (WE DO NOT BREAK USERSPACE!) カーネル開発における最初のルールであるとされています。
( つまり Linux カーネルの開発ではユーザーランドで動作するアプリケーションの動作に影響を及ぼす非互換な変更はバグとして扱われます )
そのため、通常のアプリケーションの開発者は 'kABI' を意識することはあまりなく、区別された kABI を意識するのは、カーネルスペースで動作するコードです。カーネルスペースは逆説的にこのルールの範囲外にあり、Out-of-tree( 公式なカーネル本体に含まれていないことを指す ) なカーネルモジュールは kABI の変化に追随する必要があります。( 動作の安全性のためには、カーネルのバージョンごとにリビルドを必要とするかもしれません )
幸運にも、DKMS(Dynamic Kernel Module Support) のような仕組みに乗っかったモジュールはカーネルのバージョンの変化に合わせて自動ビルドされるように構成できます。
※3
コンピューターが直接実行するコードを指し、2 進数を表す単語のバイナリとも呼ばれます。現代のコンピューターは 2 進数を取り扱うためです。
※4
例外も存在し、エミュレーション技術を用いることにより異なる OS 上でも、動作することがあります。例えば FreeBSD は Linux カーネルの ABI の実装を持っており、Linux 向けにビルドされたバイナリ ( 実行コード ) を動作させる機能を持つことが知られています。( これは厳密にはエミュレーションではありませんが )
https://docs.freebsd.org/ja/books/handbook/linuxemu/

バイナリを調べてみる

バイナリの持つ ABI をユーザーが自分で調べることも可能です、いくつか調査を行うために必要になる情報を出力してくれるツールを紹介します。

ldd

ある実行ファイルが実行時にどんな共有ライブラリをロードするかを調べる標準的なツールとして、 'ldd(1)' が知られています。

$ ldd $(which ssh)
    linux-vdso.so.1 (0x00007ffd66516000)
    libcrypto.so.3 => /lib64/libcrypto.so.3 (0x00007fd837abf000)
    libz.so.1 => /lib64/libz.so.1 (0x00007fd837aa5000)
    libcrypt.so.2 => /lib64/libcrypt.so.2 (0x00007fd837a6b000)
    libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fd837a3e000)
    libgssapi_krb5.so.2 => /lib64/libgssapi_krb5.so.2 (0x00007fd8379e7000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fd8377de000)
    libpcre2-8.so.0 => /lib64/libpcre2-8.so.0 (0x00007fd837742000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fd837fc4000)
    libkrb5.so.3 => /lib64/libkrb5.so.3 (0x00007fd837664000)
    libk5crypto.so.3 => /lib64/libk5crypto.so.3 (0x00007fd83764b000)
    libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007fd837644000)
    libkrb5support.so.0 => /lib64/libkrb5support.so.0 (0x00007fd837633000)
    libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007fd83762a000)
    libresolv.so.2 => /lib64/libresolv.so.2 (0x00007fd837616000)

この実行結果は ssh コマンドを実行したときにロードされている共有ライブラリのリストを表しています。

標準的な慣習として共有ライブラリの ABI 互換性が維持される間は同じバージョン番号を使用し、非互換を伴う変更が入った時にバージョンが変更 ( 大抵の場合はメジャーバージョンを 1 つ加算する ) し、SONAME が変わるようにします。
上の例からバージョンを読み取ると、例えば libc.so.6 は メジャーバージョンが 6 であることを示しています。※5

readelf

readelf は ELF 形式の実行ファイルの情報を表示するツールです。

$ readelf --dynamic $(which ssh)

Dynamic section at offset 0xc00c0 contains 33 entries:
  Tag    	Type                     	Name/Value
 0x0000000000000001 (NEEDED)         	Shared library: [libselinux.so.1]
 0x0000000000000001 (NEEDED)         	Shared library: [libcrypto.so.1.1]
 0x0000000000000001 (NEEDED)         	Shared library: [libdl.so.2]
 0x0000000000000001 (NEEDED)         	Shared library: [libz.so.1]
 0x0000000000000001 (NEEDED)         	Shared library: [libresolv.so.2]
 0x0000000000000001 (NEEDED)         	Shared library: [libgssapi_krb5.so.2]
 0x0000000000000001 (NEEDED)         	Shared library: [libc.so.6]
[...]

-d オプション ( 又は --dynamic オプション ) をつけることで Dynamic section を表示してくれます。 この Dynamic section には依存する共有ライブラリの情報が埋め込まれています。

abidiff

abidiff はその名前のとおり、共有ライブラリの ABI の diff を表示してくれるツールです。共有ライブラリのパスを指定して使います。

以下の例では 2 つのバージョンの OpenSSL 共有ライブラリを比較しています、
CentOS 7 向けにビルドされたパッケージに含まれる SO(Shared Object) ファイルと CentOS Stream 8 向けにビルドされた SO ファイルを比較対象としています。( この 2 つのバイナリはマイナーバージョンが異なるため、リリース当時の OpenSSL プロジェクトのポリシー ※6 では API/ABI に変更が入っていてもおかしくありません、また意図的にメジャーバージョンの異なるディストリビューションのバイナリをサンプルとしてピックアップしています )

$ abidiff  --d1 c7/rootfs --d2 cs8/rootfs  c7/rootfs/usr/lib64/libssl.so.1.0.2k cs8/rootfs/usr/lib64/libssl.so.1.1.1k

ELF SONAME changed
Functions changes summary: 0 Removed, 0 Changed, 218 Added functions
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
Function symbols changes summary: 587 Removed, 0 Added function symbols not referenced by debug info
Variable symbols changes summary: 12 Removed, 0 Added variable symbols not referenced by debug info

SONAME changed from 'libssl.so.10' to 'libssl.so.1.1'

218 Added functions:
[...]

$ echo $?
12

サマリーを見ると 218 個の関数が追加され、587 個の関数名のシンボル、12 個の変数名のシンボルが除去されていることがわかります。シンボルが除去されている場合、共有ライブラリをリンクしている実行コードから呼び出す関数へジャンプする際に名前解決ができずにエラーで終了するでしょう。
( とはいえ実際にはほとんどのケースではコードを実行する前に OS が共有ライブラリのバージョンとファイル名が異なるためロードに失敗し、実行そのものが差し止められるでしょう )

また SONAME が 'libssl.so.10' to 'libssl.so.1.1' に変化しています。バージョン名が変更されていることを含めて考えると、OpenSSL の 1.0.2k と 1.1.1k の間には ABI 互換性が無くなっていると考えていいでしょう。

この推測を abidiff の結果で裏付けてみましょう、実行例ではコマンドの終了ステータス ( シェル変数 : $?) として 12 が返ってきています。
abidiff を 開発している libabigail プロジェクトのマニュアルによると、第 3 ビット (2 進数で 4) と第 4 ビット (2 進数で 8) が立っている場合、ABI の非互換な変更が検知されたことを意味しています。

ABI をわざわざ調べなくても常識的ではありますが、OpenSSL を動的リンクしているアプリケーションで CentOS 7 向けにビルドしたバイナリは CentOS Stream 8 では動作しないということがわかります。 ( 逆もまた然りです )
多くの OS では ABI の安定性は限られたコンポーネント、さらにメジャーバージョンの範囲でのみ維持されるのが標準的で、OS のメジャーバージョンが異なれば少なくともリビルドしなければ動かないと考えたほうがいいでしょう。

ABI 変更が検知されなかった場合は、特に出力もなくコマンドは終了します。これを確かめてみるには比較してみたいバイナリファイルを用意し、 abidiff に渡してみると ABI 互換性が保たれていることを確認できます。異なるディストリビューションに対して行ってみると興味深い ( あるいは退屈な ) 結果が得られるでしょう。

※5
メジャーバージョンが 6 ということは、標準 C ライブラリに過去に 5 回の非互換な変更が入ったことがわかりますが、これには理由があります、Linux 向けに広く使われている GNU C Library は、1999 年前後に当時存在した Linux C Library とプロジェクトが合流し、合流時に Linux C Library がバージョン 5 までリリースを行っていたため、区別のためバージョンを 6 に上げたという歴史的経緯があります。
http://archive.linux.or.jp/JF/JFdocs/libc-intro.html

※6
OpenSSL プロジェクトのバージョニングポリシー の「History」には以下の記述があります。

History

From release 1.0.0, but prior to release 3.0.0, the OpenSSL versioning scheme was different and it is detailed here for historic purposes.

・Letter releases, such as 1.0.2a, exclusively contain bug and security fixes and no new features.
・Releases that change the final number, e.g. 1.1.0 vs. 1.1.1, can and are likely to contain new features, but in a way that does not break binary compatibility. This means that an application compiled and dynamically linked with 1.1.0 does not need to be recompiled when the shared library is updated to 1.1.1.
・Releases that change the second number, e.g. 1.0.0 vs 1.1.0, break both ABI and API compatibility. The primary instance of this was the transition to opaque internal structures that occurred with OpenSSL release 1.1.0.
https://www.openssl.org/policies/general/versioning-policy.html

ABI 互換にフォーカスすることで変わらないこと、可能になること

AlmaLinux は RHEL との ABI 互換を持つディストリビューションを構築するという方向にプロジェクトの方針を転換しました。目標が後退しているかのように見えますが、変わらないことがあります。それは RHEL で動くソフトウェアをそのまま問題なく動作させるという目的が依然として含まれていることです。
多くのユーザーはあらゆる側面で RHEL と同じ OS を使いたいわけではなく ( あらゆる側面で同じ物はそれ自身しか存在しません )、既存のソフトウェアやノウハウを再利用できれば充分であると考えられ、ABI 互換であればこのニーズを満たすことができます。

ポリシーの変更により、可能になることもあります、ABI 互換性を崩さない範囲であれば、アップストリームによる判断を待たずにバグ修正を行ったり、取り入れる変更についてアップストリームとは別のアプローチを試みることも許容されます。このような目的のために release-testing というリポジトリがすでに用意されています。

アップストリームとの違いを持つことにより、CentOS Stream のような上流のプロジェクトと協力をすることができるようになります。
そして AlmaLinux が自ら独自に行う変更は ABI 互換を壊さない範囲に限定されるでしょう、つまり互換性を破壊してしまうようなバージョンアップ、修正パッチのバックポート、独自パッチの追加、ビルド方法の変更などは行われないということを意味しています。

まとめ

1:1 互換にこだわることなく、ABI 互換に舵を切ったことにより、ほとんどのユーザーのニーズを満たしながらも、アップストリームの意向を尊重し、エンタープライズ Linux を構築しているエコシステムの良き市民として活動するという姿勢を打ち出しています。
また単に RHEL で動くコードがそのまま動く無償のディストリビューションという選択肢を提供するだけでなく、ダウンストリームとして独自の違いを持つことも可能になり、CentOS Stream のような上流のプロジェクトと協力することも可能になりました。この方針がエコシステムの今後の活性化に繋がることを願っています。

本記事に関連するリンク
AlamLinux OS サポートサービス ご相談・お問い合わせ
CentOS 7 延長サポートサービス
デジタルトランスフォーメーションのための電子認証基盤 iTrust
SSL/TLS サーバー証明書 SureServer Prime