gcc ビルトイン関数の呼び出し
ダイナミックリンカのソースコード(elf/rtld.c)を見ていると興味深いコメントがあった。
/* Partly clean the `bootstrap_map' structure up. Don't use `memset' since it might not be built in or inlined and we cannot make function calls at this point. Use '__builtin_memset' if we know it is available. We do not have to clear the memory if we do not have to use the temporary bootstrap_map. Global variables are initialized to zero by default. */
「memset
はビルドインでないもしくはインライン化されていない可能性があるため、ここでは呼び出してはいけない」。このコメントはダイナミックリンカの起動直後にあるため、再配置が終わっていない関数を呼び出すとアドレスが埋まっていないGOTを参照してしまい、Segmentation Faultが発生することを危惧したものだと考えられる。
ということはgccのビルトイン関数は必ずインライン化されなければならない。実際gccのビルトイン関数の説明には以下のような記述がある。
With the exception of built-ins that have library equivalents such as the standard C library functions discussed below, or that expand to library calls, GCC built-in functions are always expanded inline and thus do not have corresponding entry points and their address cannot be obtained. Attempting to use them in an expression other than a function call results in a compile-time error.
ビルトイン関数を使う小さなプログラムでも実験的に確かめることができる。
> cat main.c #include <stdio.h> int main() { int c = __builtin_clz(0x10101); printf("c = %d\n", c); return 0; } > gcc main.c -static -o main > objdump -d main | grep "<main>" -A 16 0000000000401ce5 <main>: 401ce5: f3 0f 1e fa endbr64 401ce9: 55 push %rbp 401cea: 48 89 e5 mov %rsp,%rbp 401ced: 48 83 ec 10 sub $0x10,%rsp 401cf1: c7 45 fc 0f 00 00 00 movl $0xf,-0x4(%rbp) 401cf8: 8b 45 fc mov -0x4(%rbp),%eax 401cfb: 89 c6 mov %eax,%esi 401cfd: 48 8d 3d 00 33 09 00 lea 0x93300(%rip),%rdi # 495004 <_IO_stdin_used+0x4> 401d04: b8 00 00 00 00 mov $0x0,%eax 401d09: e8 92 ee 00 00 callq 410ba0 <_IO_printf> 401d0e: b8 00 00 00 00 mov $0x0,%eax 401d13: c9 leaveq 401d14: c3 retq 401d15: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 401d1c: 00 00 00 401d1f: 90 nop
__builtin_clz
はインライン化されているようだ。