__attribute__((constructor))

main()が呼ばれる前に関数を呼ぶ方法として,一般にgcc拡張である__attribute__((constructor))が利用されている.
例えば次のコードでは,main()の前にconstructor()が呼び出される.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
__attribute__((constructor)) void constructor(){
printf("constructor\n");
}
int main(){
printf("main\n");
return 0;
}

__libc_start_main()

gccが内部で呼び出すldは,glibcの初期化を行うためにcrt*.oを静的リンクする.これによって,_start()などのシンボルがバイナリに埋め込まれることになる.
ここで埋め込まれるシンボルには未定義なものが存在する.その一つとして,crt1.oに含まれる__libc_start_main()が挙げられる.
これはmain()より先んじて呼び出される関数であり,暗黙的にcrt1.oがリンクの対象となる.しかし未定義であるがゆえに,先にソースコードに__libc_start_main()が宣言されていた場合,ldはこちらをシンボルとして採用してしまう.

どちらが先か

上記を踏まえて,次のコードではどちらの関数が先に呼び出されるだろうか.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
__attribute__((constructor)) void constructor(){
printf("constructor\n");
}
int __libc_start_main(){
printf("__libc_start_main\n");
return 0;
}
int main(){
printf("main\n");
return 0;
}

正解は__libc_start_main()だ.私は実行するまで分からなかった.

__do_global_ctors_aux()

なぜ__libc_start_main()__attribute__((constructor))よりも先に呼び出されるのか.それは__attribute__((constructor))が,__do_global_ctors_aux()によって実現されているからだ.
__do_global_ctors_aux()_start(), __libc_start_main(), __libc_csu_init(), _init()を通ってようやく呼び出される.そのため,__libc_start_main()の方がより先んじて呼び出されるのだ.

__libc_csu_init()

Sigreturn-oriented Programmingでにわかに脚光を浴びている__libc_csu_init()__libc_start_main()の引数にセットされるコンストラクタだが,シンボルを上書きして__libc_start_main()の代わりにこちらをmain()ないし__attribute__((constructor))の先に呼び出すこともできる.
当初私は__attribute__((constructor))はこの辺りから呼び出されるものだと誤解していた.

Windowsでは

今回は触れないが,.CRT$X??のセクションやThread Local Storageのコールバックによって同様の機能を実現できる.

参考文献