vdso - 仮想 ELF
動的共有オブジェクトの概要
#include <sys/auxv.h>
void *vdso = (uintptr_t) getauxval(AT_SYSINFO_EHDR);
"vDSO" (virtual dynamic shared object;
仮想動的共有オブジェクト)
は、
カーネルが自動的にすべてのユーザー空間アプリケーションのアドレス空間にマッピングを行う小さな共有ライブラリである。
vDSO はほとんどの場合 C
ライブラリから呼び出されるため、
アプリケーションは通常これらの詳細を自分では気にする必要はない。
このように、
標準関数と C
ライブラリを使って通常の方法でコードを作成することで、
vDSO
経由で利用可能な機能が活用されることになる。
いったいなぜ vDSO
は存在しているのか?
カーネルが提供するシステムコールのいくつかは、
ユーザー空間のコードがこれらのシステムコールを頻繁に呼び出すことになり、
このような呼び出しが全体の性能を支配するようになる場合がある。
これは、
呼び出しの頻度と、
ユーザー空間から抜けてカーネルに入ることによるコンテキストスイッチのオーバーヘッドの両方に起因する。
この文書の残りの部分は、
一般の開発者向けというではなく、
好奇心がある人と C
ライブラリの開発者向けの内容となっている。
もしあなたが C
ライブラリではなく自分のアプリケーションで
vDSO
を呼びだそうとしているのであれば、
ほとんどの場合間違ったことをしていることだろう。
システムコールの呼び出しは遅くなる場合がある。
x86 32
ビットシステムでは、
システムコールを呼び出したいことをカーネルに教えるためにソフトウェア割り込み
(
int $0x80)
を使うことができる。
しかしながら、この割り込みはコストがかかる処理である。
割り込みがあると、
カーネルとプロセッサーのマイクロコードの両方のすべての割り込み処理パスが実行される。
新しいプロセッサーには、
システムコール呼び出しを起動するための高速な
(だが、後方互換性がある)
命令が用意されている。
C
ライブラリが実行時にこの機能が利用できるかを確認するのではなく、
C ライブラリは vDSO
でカーネルが提供する関数を使うことができる。
用語が紛らわしい点には注意が必要である。
x86 システムでは、
システムコールを呼び出す推奨される方法を判定するのに使用される
vDSO 関数は "__kernel_vsyscall"
という名前だが、 x86=64
では、 "vsyscall"
という用語は、
カーネルに時刻はいつかや呼び出し元はどの
CPU
上にいるかを問い合わせるための廃止予定の方法も参照している。
頻繁に使用されるシステムコールの一つが
gettimeofday(2) である。
このシステムコールは、
ユーザー空間アプリケーションから直接呼び出されることも、
C
ライブラリから間接的に呼び出されることもある。
タイムスタンプが必要な場面、
タイミングループを行う場面、
ポーリングを行う場面を考えてほしい。
これらはいずれも現在時刻が何かを直ちに知りたいのが普通である。
また、この情報は秘密ではなく、
(ルートでも非特権ユーザーでも)
任意の特権モードの多くのアプリケーションが同じ情報を取得できる。
したがって、
カーネルはこの質問に応えるのに必要な情報をプロセスがアクセスできるメモリー上に配置する。
これにより、
gettimeofday(2)
はシステムコールから通常の関数コールになり、
少ないメモリーアクセスになる。
vDSO
のベースアドレスは、
(存在する場合には)
カーネルから各プログラムに初期補助ベクトル
(
getauxval(3) 参照) の
AT_SYSINFO_EHDR
タグ経由で渡される。
vDSO
がユーザーのメモリーマップの何か特定の場所にマッピングされると仮定してはならない。
通常新しいプロセスイメージが作成されるたびに
(
execve(2) 実行時点) 、
実行時にベースアドレスのランダム化が行われる。
これは "return-to-libc" 攻撃
を防ぐためにセキュリティ上の理由から行われる。
アーキテクチャーによっては
AT_SYSINFO タグもある。
このタグは vsyscall
エントリーポイントの場所を知るためだけのものであり、
しばしば省略されるか
(利用できない場合は) 0
にセットされる。
このタグは最初の vDSO
の実装で使用されていたものであり
(下記の「歴史」を参照)、
このタグを利用するのは避けるべきである。
vDSO は完全な形式の ELF
イメージなので、 vDSO
に対してシンボルの検索を行うことができる。
このため、
新しいカーネルリリースで新しいシンボルを追加することができ、
C
ライブラリが別のバージョンのカーネル上で動作する際に実行時に利用可能な機能を検出することができる。
多くの場合、 C
ライブラリは最初の呼び出し時に検出を行い、
それ以降の呼び出しで利用できるようにその結果をキャッシュする。
すべてのシンボルは (GNU
のバージョンフォーマットを使って)
バージョンが付けられている。
これにより、
カーネルは後方互換性を持たせつつ関数のシグネチャーを更新することができる。
つまり、
関数が受け取る引数や返り値が変更されることがあるということである。
したがって、 vDSO
のシンボルを検索する際には、
自分が期待する ABI
に一致するバージョンをしなければならない。
通常は vDSO
はすべてのシンボルに
"__vdso_" か "__kernel_"
というプレフィックスを付けるという慣習に従った名前付けを行っており、
他の標準のシンボルから区別することができる。
例えば、 "gettimeofday"
関数は ""__vdso_gettimeofday"
という名前になっている。
これらの関数を呼び出す場合は標準の
C
の呼び出しの慣習にしたがっておけばよい。
特殊なレジスターやスタックの動作に気を使う必要はない。
カーネルをコンパイルする際に、
vDSO
コードはコンパイルされリンクが行われる。
通常はアーキテクチャー固有のディレクトリに
vDSO
コードが生成される。
find arch/$ARCH/ -name '*vdso*.so*' -o -name '*gate*.so*'
vDSO
の名前はアーキテクチャーにより異なる。
この名前は glibc の
ldd(1)
の出力などに現れる。
名前はコードで必要となることはなく、
名前をハードコードしないこと。
ユーザー ABI |
vDSO 名 |
|
aarch64 |
linux-vdso.so.1 |
arm |
linux-vdso.so.1 |
ia64 |
linux-gate.so.1 |
mips |
linux-vdso.so.1 |
ppc/32 |
linux-vdso32.so.1 |
ppc/64 |
linux-vdso64.so.1 |
riscv |
linux-vdso.so.1 |
s390 |
linux-vdso32.so.1 |
s390x |
linux-vdso64.so.1 |
sh |
linux-gate.so.1 |
i386 |
linux-gate.so.1 |
x86-64 |
linux-vdso.so.1 |
x86/x32 |
linux-vdso.so.1 |
When tracing systems calls with
strace(1), symbols (system calls) that
are exported by the vDSO will
not appear in the trace output. Those
system calls will likewise not be visible to
seccomp(2) filters.
以下のサブ章では vDSO
のアーキテクチャー固有の注意について説明する。
使用される vDSO は、
カーネルの ABI
ではなく、
ユーザー空間コードの
ABI
に基づくことに注意すること。
したがって、
例えば、 i386 32 ビットの ELF
ライブラリ上で実行する場合、
i386 32
ビットカーネル上で実行されているか
x86-64 64
ビットカーネル上で実行されているかに関わらず同じ
vDSO が得られる。
したがって、
以下のどの節が関係するかを判断する際にはユーザー空間
ABI
の名前を使用する必要がある。
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
シンボル |
バージョン |
|
__vdso_gettimeofday |
LINUX_2.6 (Linux 4.1 以降で公開) |
__vdso_clock_gettime |
LINUX_2.6 (Linux 4.1 以降で公開) |
Additionally, the ARM port has a code page full of utility functions. Since it's
just a raw page of code, there is no ELF information for doing symbol lookups
or versioning. It does provide support for different versions though.
For information on this code page, it's best to refer to the kernel
documentation as it's extremely detailed and covers everything you need to
know:
Documentation/arm/kernel_user_helpers.txt.
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
シンボル |
バージョン |
|
__kernel_rt_sigreturn |
LINUX_2.6.39 |
__kernel_gettimeofday |
LINUX_2.6.39 |
__kernel_clock_gettime |
LINUX_2.6.39 |
__kernel_clock_getres |
LINUX_2.6.39 |
As this CPU lacks a memory management unit (MMU), it doesn't set up a vDSO in
the normal sense. Instead, it maps at boot time a few raw functions into a
fixed location in memory. User-space applications then call directly into that
region. There is no provision for backward compatibility beyond sniffing raw
opcodes, but as this is an embedded CPU, it can get away with
things—some of the object formats it runs aren't even ELF based
(they're bFLT/FLAT).
For information on this code page, it's best to refer to the public
documentation:
http://docs.blackfin.uclinux.org/doku.php?id=linux-kernel:fixed-code
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
シンボル |
バージョン |
|
__kernel_gettimeofday |
LINUX_2.6 (Linux 4.4 以降で公開) |
__kernel_clock_gettime |
LINUX_2.6 (Linux 4.4 以降で公開) |
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
シンボル |
バージョン |
|
__kernel_sigtramp |
LINUX_2.5 |
__kernel_syscall_via_break |
LINUX_2.5 |
__kernel_syscall_via_epc |
LINUX_2.5 |
The Itanium port is somewhat tricky. In addition to the vDSO above, it also has
"light-weight system calls" (also known as "fast syscalls"
or "fsys"). You can invoke these via the
__kernel_syscall_via_epc vDSO helper. The system calls listed here have
the same semantics as if you called them directly via
syscall(2), so
refer to the relevant documentation for each. The table below lists the
functions available via this mechanism.
関数 |
|
clock_gettime |
getcpu |
getpid |
getppid |
gettimeofday |
set_tid_address |
The parisc port has a code page with utility functions called a gateway page.
Rather than use the normal ELF auxiliary vector approach, it passes the
address of the page to the process via the SR2 register. The permissions on
the page are such that merely executing those addresses automatically executes
with kernel privileges and not in user space. This is done to match the way
HP-UX works.
Since it's just a raw page of code, there is no ELF information for doing symbol
lookups or versioning. Simply call into the appropriate offset via the branch
instruction, for example:
ble <offset>(%sr2, %r0)
オフセット |
関数 |
|
00b0 |
lws_entry (CAS operations) |
00e0 |
set_thread_pointer (used by glibc) |
0100 |
linux_gateway_entry (syscall) |
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
*
のマークが付いた関数は、カーネルが
PowerPC64 (64 ビット)
カーネルの場合にだけ利用可能である。
シンボル |
バージョン |
|
__kernel_clock_getres |
LINUX_2.6.15 |
__kernel_clock_gettime |
LINUX_2.6.15 |
__kernel_datapage_offset |
LINUX_2.6.15 |
__kernel_get_syscall_map |
LINUX_2.6.15 |
__kernel_get_tbfreq |
LINUX_2.6.15 |
__kernel_getcpu *
|
LINUX_2.6.15 |
__kernel_gettimeofday |
LINUX_2.6.15 |
__kernel_sigtramp_rt32 |
LINUX_2.6.15 |
__kernel_sigtramp32 |
LINUX_2.6.15 |
__kernel_sync_dicache |
LINUX_2.6.15 |
__kernel_sync_dicache_p5 |
LINUX_2.6.15 |
The
CLOCK_REALTIME_COARSE and
CLOCK_MONOTONIC_COARSE clocks are
not supported by the
__kernel_clock_getres and
__kernel_clock_gettime interfaces; the kernel falls back to the real
system call.
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
シンボル |
バージョン |
|
__kernel_clock_getres |
LINUX_2.6.15 |
__kernel_clock_gettime |
LINUX_2.6.15 |
__kernel_datapage_offset |
LINUX_2.6.15 |
__kernel_get_syscall_map |
LINUX_2.6.15 |
__kernel_get_tbfreq |
LINUX_2.6.15 |
__kernel_getcpu |
LINUX_2.6.15 |
__kernel_gettimeofday |
LINUX_2.6.15 |
__kernel_sigtramp_rt64 |
LINUX_2.6.15 |
__kernel_sync_dicache |
LINUX_2.6.15 |
__kernel_sync_dicache_p5 |
LINUX_2.6.15 |
The
CLOCK_REALTIME_COARSE and
CLOCK_MONOTONIC_COARSE clocks are
not supported by the
__kernel_clock_getres and
__kernel_clock_gettime interfaces; the kernel falls back to the real
system call.
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
シンボル |
バージョン |
|
__kernel_rt_sigreturn |
LINUX_4.15 |
__kernel_gettimeofday |
LINUX_4.15 |
__kernel_clock_gettime |
LINUX_4.15 |
__kernel_clock_getres |
LINUX_4.15 |
__kernel_getcpu |
LINUX_4.15 |
__kernel_flush_icache |
LINUX_4.15 |
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
シンボル |
バージョン |
|
__kernel_clock_getres |
LINUX_2.6.29 |
__kernel_clock_gettime |
LINUX_2.6.29 |
__kernel_gettimeofday |
LINUX_2.6.29 |
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
シンボル |
バージョン |
|
__kernel_clock_getres |
LINUX_2.6.29 |
__kernel_clock_gettime |
LINUX_2.6.29 |
__kernel_gettimeofday |
LINUX_2.6.29 |
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
シンボル |
バージョン |
|
__kernel_rt_sigreturn |
LINUX_2.6 |
__kernel_sigreturn |
LINUX_2.6 |
__kernel_vsyscall |
LINUX_2.6 |
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
シンボル |
バージョン |
|
__kernel_sigreturn |
LINUX_2.5 |
__kernel_rt_sigreturn |
LINUX_2.5 |
__kernel_vsyscall |
LINUX_2.5 |
. |
|
. |
|
__vdso_clock_gettime |
LINUX_2.6 (Linux 3.15 以降で公開) |
__vdso_gettimeofday |
LINUX_2.6 (Linux 3.15 以降で公開) |
__vdso_time |
LINUX_2.6 (Linux 3.15 以降で公開) |
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
これらのシンボルはすべて
"__vdso_"
のプレフィックスなしでも利用できるが、
これらは無視し、
以下の名前だけを使うこと。
シンボル |
バージョン |
|
__vdso_clock_gettime |
LINUX_2.6 |
__vdso_getcpu |
LINUX_2.6 |
__vdso_gettimeofday |
LINUX_2.6 |
__vdso_time |
LINUX_2.6 |
以下のテーブルは vDSO
で公開されるシンボルの一覧である。
シンボル |
バージョン |
|
__vdso_clock_gettime |
LINUX_2.6 |
__vdso_getcpu |
LINUX_2.6 |
__vdso_gettimeofday |
LINUX_2.6 |
__vdso_time |
LINUX_2.6 |
vDSO は元々は一つの関数
vsyscall であった。
古いカーネルでは、
プロセスのメモリーマップに
"vdso"
ではなくこの名前が見えるかもしれない。
時間が経つに連れて、
この仕組みはより多くの機能をユーザー空間に渡す有効な方法であると認識されるようになり、
現在の形の vDSO
という形に見直しが行われた。
syscalls(2),
getauxval(3),
proc(5)
Linux
のソースコードツリーのドキュメント、例、ソースコード:
Documentation/ABI/stable/vdso
Documentation/ia64/fsys.txt
Documentation/vDSO/* (vDSO の使用例がある)
find arch/ -iname '*vdso*' -o -iname '*gate*'
この man ページは Linux
man-pages
プロジェクトのリリース
5.10
の一部である。プロジェクトの説明とバグ報告に関する情報は
https://www.kernel.org/doc/man-pages/
に書かれている。