エンジニアならC言語プログラムの終わりに呼び出されるexit()の中身分かってますよね?:main()関数の前には何があるのか(9)(3/3 ページ)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。今回は、プログラムの終わりに呼び出されるexit()の中身を探る。
exit()の処理を読む
exit()で行われている処理も見てみよう。
ソースコードはどこにあるだろうか。man 3 exitを参照すると、以下のようになっている。
EXIT(3) Linux Programmer's Manual EXIT(3) NAME exit - cause normal process termination SYNOPSIS #include <stdlib.h> void exit(int status); ...
#includeの部分を見てほしい。exit()はstdlib.hでプロトタイプ宣言がされているようだ。
そしてglibcのトップディレクトリを見ると、stdlibというディレクトリがある。ここにexit()の本体があるのではないだろうか。
[user@localhost ~]$ cd glibc-2.21 [user@localhost glibc-2.21]$ ls stdlib/*exit* stdlib/at_quick_exit.c stdlib/exit.h stdlib/atexit.c stdlib/old_atexit.c stdlib/cxa_at_quick_exit.c stdlib/on_exit.c stdlib/cxa_atexit.c stdlib/quick_exit.c stdlib/cxa_thread_atexit_impl.c stdlib/tst-tls-atexit-lib.c stdlib/exit.c stdlib/tst-tls-atexit.c [user@localhost glibc-2.21]$
exit.cというファイルがある。見てみると、exit()が以下のように定義されていた。
101:void 102:exit (int status) 103:{ 104: __run_exit_handlers (status, &__exit_funcs, true); 105:}
__run_exit_handlers()という関数が呼ばれるようだ。そして__run_exit_handlers()は同一ファイル内で以下のように定義されていた。
28:/* Call all functions registered with `atexit' and `on_exit', 29: in the reverse of the order in which they were registered 30: perform stdio cleanup, and terminate program execution with STATUS. */ 31:void 32:attribute_hidden 33:__run_exit_handlers (int status, struct exit_function_list **listp, 34: bool run_list_atexit) 35:{ ... 42: /* We do it this way to handle recursive calls to exit () made by 43: the functions registered with `atexit' and `on_exit'. We call 44: everyone on the list and use the status value in the last 45: exit (). */ 46: while (*listp != NULL) 47: { 48: struct exit_function_list *cur = *listp; 49: 50: while (cur->idx > 0) 51: { 52: const struct exit_function *const f = 53: &cur->fns[--cur->idx]; 54: switch (f->flavor) 55: { 56: void (*atfct) (void); 57: void (*onfct) (int status, void *arg); 58: void (*cxafct) (void *arg, int status); ... 77: case ef_cxa: 78: cxafct = f->func.cxa.fn; 79:#ifdef PTR_DEMANGLE 80: PTR_DEMANGLE (cxafct); 81:#endif 82: cxafct (f->func.cxa.arg, status); 83: break; 84: } ... 97: _exit (status); 98:}
最後に_exit()が呼ばれていることが確認できる。
またwhile()でループしているのは、おそらくatexit()の処理だろう。atexit()により登録された関数が、ここで順次呼ばれていくわけだ。
atexit()の処理を読む
atexit()のソースコードも見ておこう。man atexitではstdlib.hをインクルードするように書いてあるので、stdlibというディレクトリに本体がありそうだ。
見てみるとstdlib/atexit.cというファイルがある。そしてその中でatexit()は以下のように定義されている。
43:/* Register FUNC to be executed by `exit'. */ 44:int 45:#ifndef atexit 46:attribute_hidden 47:#endif 48:atexit (void (*func) (void)) 49:{ 50: return __cxa_atexit ((void (*) (void *)) func, NULL, 51: &__dso_handle == NULL ? NULL : __dso_handle); 52:}
__cxa_atexit()という関数を呼んでいるようだ。これはどこにあるだろうか。
[user@localhost glibc-2.21]$ grep __cxa_atexit */* ... stdlib/cxa_atexit.c:__cxa_atexit (void (*func) (void *), void *arg, void *d) ...
stdlib/cxa_atexit.cを見ればいいようだ。
52:/* Register a function to be called by exit or when a shared library 53: is unloaded. This function is only called from code generated by 54: the C++ compiler. */ 55:int 56:__cxa_atexit (void (*func) (void *), void *arg, void *d) 57:{ 58: return __internal_atexit (func, arg, d, &__exit_funcs); 59:}
今度は_ _internal_atexit()が呼ばれている。そして_ _internal_atexit()は同一ファイル内で、以下のように定義されている。
30:int 31:attribute_hidden 32:__internal_atexit (void (*func) (void *), void *arg, void *d, 33: struct exit_function_list **listp) 34:{ 35: struct exit_function *new = __new_exitfn (listp); 36: 37: if (new == NULL) 38: return -1; 39: 40:#ifdef PTR_MANGLE 41: PTR_MANGLE (func); 42:#endif 43: new->func.cxa.fn = (void (*) (void *, int)) func; 44: new->func.cxa.arg = arg; 45: new->func.cxa.dso_handle = d; 46: atomic_write_barrier (); 47: new->flavor = ef_cxa; 48: return 0; 49:}
第4引数で渡されるlistpに対して新たなエントリとして、第1引数で渡される関数を登録するようだ。呼び出し元の__cxa_atexit()を見ると、これには&__exit_funcsというアドレスが渡されている。これがatexit()の関数のリストだ。
もう一度「exit()の処理を読む」の項のexit()を見直してみよう。
__exit_funcsを第2引数にして、__run_exit_handlers()を呼んでいる。これが__run_exit_handlers()に渡され、登録された関数を順次実行しているわけだ。
書籍紹介
ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ
坂井弘亮著
秀和システム 3,200円
C言語の入門書では、"Hello, World"と出力するプログラムを最初に作るのが定番です。"Hello, World"は、たった7行の単純なプログラムですが、printf()の先では何が行われているのか、main()の前にはいったい何があるのか、考えてみると謎だらけです。本書は、基礎中の基礎である"Hello, World"プログラムを元に、OSと標準ライブラリの仕組みをあらゆる角度からとことん解析します。資料に頼らず、自分の手で調べる方法がわかります。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- プログラミング言語Cについて知ろう
プログラミング言語の基本となる「C」。正しい文法や作法を身に付けよう。Cには確かに学ぶだけの価値がある(編集部) - シェルコード解析に必携の「5つ道具」
コンピュータウイルスの解析などに欠かせないリバースエンジニアリング技術ですが、何だか難しそうだな、という印象を抱いている人も多いのではないでしょうか。この連載では、「シェルコード」を例に、実践形式でその基礎を紹介していきます。(編集部) - 【 od 】コマンド――ファイルを8進数や16進数でダンプする
本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回は、「od」コマンドです。