プログラム実行の流れ
- _start()
- __libc_start_main()
- exit(main())
_start()
glibc/sysdeps/i386/start.Sに定義される。
# _start()はエントリーポイントであり、テキストセグメントの先頭に配置される。
# edxはatexit()に登録される関数ポインタを持ち、espは引数や環境変数を指すが、他のレジスタは規定されない。
_start:
# 最外フレームを明示する為、フレームポインタをクリアする。
xorl %ebp, %ebp
# esiにargc、ecxにargvを設定する。
# envpは__libc_start_main()で設定される。
popl %esi
movl %esp, %ecx
# SSE命令は16バイトのアライメントを要求する。
# 28バイト使用する為、4バイトのパディングを行う。
andl $0xfffffff0, %esp
pushl %eax
# スタックとatexit()に登録される関数ポインタを渡す。
pushl %esp
pushl %edx
# .initと.finiを渡す。
pushl $__libc_csu_fini
pushl $__libc_csu_init
# argcとargvを渡す。
pushl %ecx
pushl %esi
# main()のアドレスを渡す。
pushl $main
# __libc_start_main()を呼ぶ。
call __libc_start_main
# exit()で終了しない場合はクラッシュさせる。
hlt
__libc_start_main()
glibc/csu/libc-start.cに定義される。
# LIBC_START_MAINは__libc_start_mainに置き換わる。
#define LIBC_START_MAIN __libc_start_main
static int LIBC_START_MAIN(int (*main)(int, char **, char **),
int argc, char **argv,
int (*init)(int, char **, char **), void (*fini)(void),
void (*rtld_fini)(void), void *stack_end) {
# 環境変数を設定する。
__environ = &argv[argc + 1];
# 関数を登録し、初期化を行う。
if(rtld_fini) {
__cxa_atexit(rtld_fini, NULL, NULL);
}
if(fini) {
__cxa_atexit(fini, NULL, NULL);
}
if(init) {
init(argc, argv, __environ);
}
# exit(main())を呼び出す。
exit(main(argc, argv, __environ));
}
exit()
_exit()はhlt命令を含む為、アーキテクチャ依存のアセンブラを調べる。
glibc/sysdeps/unix/sysv/linux/i386/_exit.Sに定義される。
_exit:
# ステータスをシステムコールに渡す。
movl 4(%esp), %ebx
# exit_groupを実行する。
# ENTER_KERNELはシステムコールを実行するマクロである。
#ifdef __NR_exit_group
movl $__NR_exit_group, %eax
ENTER_KERNEL
#endif
# exit_groupが存在しない場合、exitを実行する。
movl $__NR_exit, %eax
int $0x80
# プログラムが終了しない場合はクラッシュさせる。
hlt
exitは呼び出したスレッド、exit_groupは全スレッドを終了させるシステムコールである。
_exit()はexitやexit_groupを実行し、プログラムを終了させるシステムコールラッパーである。
exit()はatexit()に登録された関数を実行した後、_exit()を呼び出す。
APIとABI
カーネルはシステムコールのABIであるexitやexit_groupを提供し、glibcはPOSIXのAPIであるexit()や_exit()を実装する。
FreeBSDにはexit_groupは存在しないが、カーネルの差はシステムコールラッパーで埋められる。
カーネル側の処理
glibcを見てきたが、カーネル側の処理も確認する。
UNIXライクなシステムでは、fork()とexec()でプログラムを開始する。
exec()はexecveを呼び出し、以下の処理を行う。
- 実行ファイルを仮想メモリに配置する
- 引数やデータ領域、環境変数を準備する
- エントリーポイントにジャンプする
コマンド
- manのカテゴリ1はコマンド、カテゴリ2はシステムコールラッパー、カテゴリ3はライブラリ関数である
- straceを使用するとexit_groupの呼び出しが確認できる