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はインライン化されているようだ。