プログラムの実行はどのようにして行われるのか、Linuxカーネルのコードから探る:main()関数の前には何があるのか(終)(1/2 ページ)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。最終回は、Linuxカーネルの中では、プログラムの起動時にはどのような処理が行われているのかを探る。
書籍の中から有用な技術情報をピックアップして紹介する本シリーズ。今回は、秀和システム発行の書籍『ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ(2015年9月11日発行)』からの抜粋です。
ご注意:本稿は、著者及び出版社の許可を得て、そのまま転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。
※編集部注:前回記事「エンジニアならC言語プログラムの終わりに呼び出されるexit()の中身分かってますよね?」はこちら
Linuxカーネルの処理を見てみよう
ここまでは主にglibcが持つスタートアップと終了処理について見てきた。
しかしOSカーネルの中では、プログラムの起動時にはどのような処理が行われているのだろうか。
例えばプログラムの実行はエントリ・ポイントから開始される、という説明をした。ということはエントリ・ポイントをEIPに設定して実行を開始するような処理が、どこかで行われているはずだ。
次はLinuxカーネルでのプロセス起動処理を見てみよう。
プログラムの実行はどのようにして行われるのか
そもそもプログラムの実行は、どのようにして行われるのであろうか。
UNIXライクなシステムでは、新しいプロセスはfork()により生成され、exec()系の関数により新たなプログラムに書きかわることで実行されるというのが基本形だ。「基本形」と言っているのはvfork()などの新たなシステムコールもあるからだが、とりあえずは気にしなくていいだろう。
また「exec()系」というのはプログラムを実行するためのライブラリ関数としてexeclp()やexecvp()などがあり、これらを総称して「exec()系」と俗に呼ばれる。最終的にはexecveシステムコールを発行するため、システムコールとしてはexecveに集約される。
つまりプログラムの起動はfork→execveという流れによって行われる。
execveシステムコールが発行されたら、あとはカーネルの仕事だ。そしてこのときに必要な作業は、以下のようなものだろうか。
- 実行ファイルを読み込み、仮想メモリ上にマッピングする
- argc/argv[]、BSSの初期化、環境変数の引き渡しなどを行う
- 実行ファイル上のエントリ・ポイントから実行を開始する
これらの処理をOSのカーネルが行っていることになる。そしてたとえばLinuxならば、そのソースコードを読めば、実際にどのような処理が行われるのかがわかるはずだ。
ここでは新たなプロセスを開始するための、カーネル内処理を見てみよう。
execve()の処理
まずはexecveシステムコールの処理関数を探してみよう。
これはシステムコール・テーブルを見ればわかるだろうか。Linuxカーネル・ソースコードのarch/x86/kernel/syscall_table_32.Sを見てみると、以下のように登録されている。
1:ENTRY(sys_call_table) ... 12: .long sys_unlink /* 10 */ 13: .long ptregs_execve ...
システムコール番号は11のようだ。さらにexecve で検索すると、kernel/entry_32.Sに以下のような定義があることに気がつく。
715:/* 716: * System calls that need a pt_regs pointer. 717: */ 718:#define PTREGSCALL(name) \ 719: ALIGN; \ 720:ptregs_##name: \ 721: leal 4(%esp),%eax; \ 722: jmp sys_##name; ... 728:PTREGSCALL(execve) ...
ptregs_execveというシンボルが定義され、そこからsys_execveが呼ばれる、ということになるようだ。つまりsys_execveを探せばいいことになる。
これはアーキテクチャ非依存部分にあるだろうから、トップ・ディレクトリ付近で検索すればいいだろう…と思ったのだが実際にやっても見つからない。気を取りなおしてx86依存部で検索すると、kernel/process_32.cというファイルが見つかり、sys_execve()が以下のように定義されていた。
447:/* 448: * sys_execve() executes a new program. 449: */ 450:int sys_execve(struct pt_regs *regs) 451:{ ... 459: error = do_execve(filename, 460: (char __user * __user *) regs->cx, 461: (char __user * __user *) regs->dx, 462: regs); ...
do_execve()が呼ばれるようだ。これを探してみると、今度こそアーキテクチャ非依存部のfs/exec.cに見つかり、以下のように定義されていた。
1357:/* 1358: * sys_execve() executes a new program. 1359: */ 1360:int do_execve(char * filename, 1361: char __user *__user *argv, 1362: char __user *__user *envp, 1363: struct pt_regs * regs) 1364:{ ... 1426: retval = copy_strings(bprm->argc, argv, bprm); 1427: if (retval < 0) 1428: goto out; 1429: 1430: current->flags &= ~PF_KTHREAD; 1431: retval = search_binary_handler(bprm,regs); ...
copy_strings()という関数によってargv[]の準備が行われているようだ。
ELFフォーマットのロード
手始めにdo_execve()の処理を見てみたが、別の目線からもexecve()の処理を見てみよう。
実行ファイルはELFフォーマットという形式になっているので、ELFフォーマットの解析処理を行っている箇所があるはずだ。そしてこれはアーキテクチャ非依存部にあるだろう。
ファイル名に「elf」を含むようなファイルは無いだろうか。
[user@localhost ~]$ cd linux-2.6.32.65 [user@localhost linux-2.6.32.65]$ ls */*elf* fs/binfmt_elf.c lib/locking-selftest-softirq.h fs/binfmt_elf_fdpic.c lib/locking-selftest-spin-hardirq.h fs/compat_binfmt_elf.c lib/locking-selftest-spin-softirq.h lib/locking-selftest-hardirq.h lib/locking-selftest-spin.h lib/locking-selftest-mutex.h lib/locking-selftest-wlock-hardirq.h lib/locking-selftest-rlock-hardirq.h lib/locking-selftest-wlock-softirq.h lib/locking-selftest-rlock-softirq.h lib/locking-selftest-wlock.h lib/locking-selftest-rlock.h lib/locking-selftest-wsem.h lib/locking-selftest-rsem.h lib/locking-selftest.c [user@localhost linux-2.6.32.65]$
fs/binfmt_elf.cというファイルがそれらしく思える。
そしてbinfmt_elf.cの中身を見てみると、load_elf_binary()という関数がある。名前からして、まさにELFフォーマットの実行ファイルのロード処理のように思える。
連載第8回で説明したように、プログラムの実行はELFフォーマットに含まれているエントリ・ポイントから開始される。ということはこの中に、エントリ・ポイントを扱っている箇所があるのではないだろうか。
「entry」というキーワードで検索すると、以下のような箇所が見つかった。
563:static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs) 564:{ ... 978: start_thread(regs, elf_entry, bprm->p); ...
start_thread()という関数を呼び出している。さらにレジスタ関連らしき引数と、エントリ・ポイントのアドレスを渡しているようだ。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- プログラミング言語Cについて知ろう
プログラミング言語の基本となる「C」。正しい文法や作法を身に付けよう。Cには確かに学ぶだけの価値がある(編集部) - シェルコード解析に必携の「5つ道具」
コンピュータウイルスの解析などに欠かせないリバースエンジニアリング技術ですが、何だか難しそうだな、という印象を抱いている人も多いのではないでしょうか。この連載では、「シェルコード」を例に、実践形式でその基礎を紹介していきます。(編集部) - 【 od 】コマンド――ファイルを8進数や16進数でダンプする
本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回は、「od」コマンドです。