LD_AUDITとGlobal Offset Table
GNU製のLinux向け動的リンカld-linux.so
には環境変数 LD_AUDIT
経由で使える監視APIがあります。このAPIを使えば、ld-linux.so
の様々な挙動にフックして、その挙動を監視・干渉することができます。また、興味深いことにLD_AUDIT
を有効にするとGOT(Global Offset Table)周りの挙動も変化します。
PLT(Procedure Linkage Table)やGOTについて知らない場合はこちらの記事が分かりやすいです。この記事のglibcはすべてコミット 137ed5ac44を参照しています。
動作例
LD_AUDIT
を使ってltrace(1)
まがいのものを作ってみます。以下の3つのファイルを作ります。なお、このサンプルはLinux glibcで、LD_AUDIT機能による関数トレースを参考にしました。
audit.c
#define _GNU_SOURCE #include <link.h> #include <stdio.h> unsigned int la_version(unsigned int version) { return LAV_CURRENT; } unsigned int la_objopen(struct link_map* map, Lmid_t lmid, uintptr_t* cookie) { printf("[LD_AUDIT] %s loaded\n", map->l_name); return LA_FLG_BINDTO | LA_FLG_BINDFROM; } ElfW(Addr) la_x86_64_gnu_pltenter(ElfW(Sym) * sym, unsigned int ndx, uintptr_t* refcook, uintptr_t* defcook, La_x86_64_regs* regs, unsigned int* flags, const char* symname, long* framesizep) { printf("[LD_AUDIT] %s entering\n", symname); return sym->st_value; }
hello.c
#include <stdio.h> int main() { puts("Hello World!"); puts("Hello World!"); puts("Hello World!"); return 0; }
run.sh
#! /bin/bash -eux gcc -o libaudit.so -shared -fpic -fPIC -Wl,-soname,libaudit.so audit.c gcc -o hello hello.c
run.sh
を実行すると以下のような結果が得られて、ltraceっぽいことができているのがわかります。
> ./run.sh + gcc -o libaudit.so -shared -fpic -fPIC -Wl,-soname,libaudit.so audit.c + gcc -o hello hello.c + LD_AUDIT=libaudit.so + ./hello [LD_AUDIT] loaded [LD_AUDIT] /lib64/ld-linux-x86-64.so.2 loaded [LD_AUDIT] /lib/x86_64-linux-gnu/libc.so.6 loaded [LD_AUDIT] _dl_find_dso_for_object entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] puts entering Hello World! [LD_AUDIT] puts entering Hello World! [LD_AUDIT] puts entering Hello World! + LD_BIND_NOW=1 + LD_AUDIT=libaudit.so
解説
la_version
はLD_AUDIT
を使うために必ず必要な関数、la_objopen
は新しいshared objectがロードされたときに呼び出される関数であり、今回は深入りしません。
la_x86_64_gnu_pltenter
は PLT(Procedure Linkage Table)がGOT(Global Offset Table)を埋めるときに呼び出される関数であり、具体的にはここで呼び出されます。
この実行結果で興味深いのは [LD_AUDIT] puts entering
が複数回出力されていることです。一度PLTのエントリがGOTに正しいアドレスを埋めた後は、puts
の呼び出しはlibcのアドレスを埋める部分を通らず、libc中のputs
本体に直接jmpしla_x86_64_gnu_pltenter
を呼び出すとことはできないはずです。
ここから考えるとLD_AUDIT
が設定されているときはGOTにアドレスを埋める処理が行われない、と推測できます。glibcの実装を確認してみましょう。
LD_AUDIT
によって設定されたla_x86_64_gnu_pltenter
はdl-runtime.cのL435-L439で呼び出されます。- これは dl-runtime.cの_dl_profile_fixupの中から呼ばれています。
- また、
_dl_profile_fixup
にはGOTの値を更新する処理がありません。 - 一方、dl-runtime.cには _dl_fixupもあり、
LD_AUDIT
の有無で_dl_profile_fixup
と_dl_fixup
を切り替えているのだと推察できます。 _dl_fixup
は dl-trampoline.hの_dl_runtime_resolveから、_dl_profile_fixup
は dl-trampoline.hの_dl_runtime_profile からそれぞれ呼び出されています。- これら2つの関数のどちらかが dl-machine.hで
got[2]
に設定されます。 - この分岐を決定するのは
elf_machine_runtime_setup
のprofile
引数で dynamic-link.hのELF_DYNAMIC_RELOCATEが呼び出し元です。 - このマクロはdl-reloc.cで呼び出されています。
- dl-reloc.cの_dl_relocate_objectの最初の方で
consider_profiling
がLD_AUDIT
の有無に従って設定されています。よって、LD_AUDIT
が設定されているときは_dL_profile_fixup
が呼び出され、GOTが更新されないことがわかります。
また、dl-machine.hにはprofiling extensionが使用されている場合、GOTを書き換えない旨のコメントがあります。
/ The got[2] entry contains the address of a function which gets called to get the address of a so far unresolved function and jump to it. The profiling extension of the dynamic linker allows to intercept the calls to collect information. In this case we don't store the address in the GOT so that all future calls also end in this function. /