glibc は read(2) や write(2) などの C 関数を公開し、Linux はアーキテクチャ固有のシステムコール入口規約を公開します。
ラッパーはこれらの層を橋渡しします。公開 C ABI を受け入れ、選択したアーキテクチャのシステムコール ABI に従い、
カーネルエラーを変換し、POSIX スレッドキャンセルの動作を実装する場合があります。
以下の例は現在の上流ソースツリーを説明します。リリース版 glibc は異なるファイルやマクロ定義を選択できるため、 ソースを調べるときは、対象のリリースとビルドするターゲットトリプルを最初に確認します。
sysdeps/ の選択
#
sysdeps/ ツリー には、オペレーティングシステム、
CPU、ABI、ワードサイズ、機能に基づいて選択される実装があります。汎用コードはフォールバックであり、
より具体的なディレクトリが同じ相対名のファイルを置き換えられます。Linux ビルドでは sysdeps/unix/sysv/linux/ などが関係し、
アーキテクチャが Linux 固有の入口コードや ABI 固有の変種をさらに提供する場合があります。
glibc の configure とビルド機構は、適用可能なシステム依存ディレクトリの順序付きリストを構築します。
ビルドルールはそのリストを検索し、選択された実装が汎用フォールバックより優先されるようにします。
したがって、この構造は各ファイルが正確に 1 つの軸だけを持つ分類体系ではありません。
OS ディレクトリがアーキテクチャヘッダーに依存することも、アーキテクチャディレクトリが OS 非依存コードを含むこともあります。
1 つのディレクトリ名から動作を推測せず、config.make、設定済みビルドディレクトリ、選択されたソースパスを調べます。
生成される薄いラッパー #
一部の薄いアセンブリラッパーは
syscalls.list.
各レコードは出力、Linux システムコール、引数シグネチャ、強シンボル、任意の弱シンボルまたはバージョン付き名前を指定します。
ビルドは
make-syscalls.sh,
を呼び出します。このスクリプトは次を行います。
- 優先度の高い
sysdepsディレクトリで上書きされたエントリを削除する。 - ターゲットの
sysdep.hを通じてSYS_番号を解決する。 - シグネチャから引数個数と特殊な引数処理を導出する。
syscall-template.Sを使う規則を出力し、リストが要求する場合はエイリアスと互換性 シンボルも含める。
syscalls.list がすべての公開関数を定義するわけではありません。検証、フォールバックロジック、
キャンセル処理、時間幅変換、その他のポリシーを持つラッパーは、手書きであることがよくあります。
現在の glibc では write がそのような手書き C ラッパーの 1 つです。
write.c;
そのため、生成経路との比較に適しています。
write とキャンセル対応ディスパッチ
#
概念的には、Linux の write ラッパーは次を行います。
ssize_t
write(int fd, const void *buffer, size_t count)
{
return cancellation_aware_syscall(SYS_write, fd, buffer, count);
}実際のソースは SYSCALL_CANCEL (write, fd, buf, nbytes) を呼び出します。このマクロはキャンセルチェックが不要な場合に
インラインのシステムコール経路を選択します。それ以外では、スレッドのキャンセル型を一時的に変更して呼び出しを実行し、
以前の型を復元します。write は POSIX のキャンセルポイントであるため、この境界により、
すべての内部システムコールをキャンセルポイントとして扱わずに保留中のキャンセルを実行できます。
このマクロは、アーキテクチャのインラインシステムコール基本処理に到達する前に引数個数でディスパッチします。 Linux では、その基本処理がターゲット ABI で規定されたレジスターにシステムコール番号と引数を配置し、 アーキテクチャのシステムコール命令を実行して、カーネルの戻り値を受け取ります。C 関数 ABI もレジスター列も普遍的ではありません。 x86-64、i386、AArch64、その他の移植先では異なるレジスターと破壊規則を使います。
カーネルは低レベルの規約でエラーを返します。選択された glibc のサポートコードはエラー範囲を認識し、
スレッドローカルな errno に正のエラー番号を保存し、通常の公開ラッパーに -1 を返させます。
生のエラー値を意図的に公開するインターフェースは、異なる内部規約を使います。
glibc manual’s system-call section
はアプリケーションレベルの契約を文書化し、ソースマクロはアーキテクチャ固有の境界を実装します。
公開名、隠し定義、エイリアス #
write.c の定義は __libc_write という名前であり、ソースは内部可視性と __write や公開名 write などのエイリアスを宣言します。
この形は実装の同一性と公開 ABI の同一性を分離します。
libc_hidden_defとlibc_hidden_weakにより、libc 内部の呼び出しは適切な場合に隠し定義へバインドでき、 置換可能な公開シンボル検索を避けられます。- 強エイリアスは別の内部名に同じ実装を与えます。
- 弱い公開エイリアスは、ビルドや ABI がその動作を必要とする場合に別の定義で上書きできます。
- シンボルバージョン機構は公開名をエクスポートする ABI バージョンを記録し、古いバイナリー向けの互換性エントリーポイントを保持できます。
hidden は glibc 内部の参照に影響しますが、共有ライブラリ ABI から文書化された公開シンボルを消すものではありません。
未解決シンボルを診断する場合は、アーキテクチャの libc.so を
readelf --dyn-syms --version-info で調べ、ソースおよびリリースのバージョンスクリプトと比較します。
エイリアス宣言だけでは、配布ライブラリがエクスポートするバージョンの十分な証拠にはなりません。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)