OSのシステムコールの呼び出しとは&バイナリエディタの使い方:main()関数の前には何があるのか(4)(1/3 ページ)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。今回は、printf()内のポインタ経由での関数呼び出しが行き着く先にあるシステムコールの呼び出しとバイナリエディタの使い方について。
書籍の中から有用な技術情報をピックアップして紹介する本シリーズ。今回は、秀和システム発行の書籍『ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ(2015年9月11日発行)』からの抜粋です。
ご注意:本稿は、著者及び出版社の許可を得て、そのまま転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。
※編集部注:前回記事「試行錯誤のデバッグで探る、printf()内のポインタ経由での関数呼び出しが行き着く先とは」はこちら
write()が呼ばれている
write()はファイル出力を行うためのシステムコールの呼び出しだ。
ということは、ここでメッセージ出力が行われているのではないだろうか。stepiで関数の内部に入ってみよう。
メッセージ出力がされないかどうか、ステップ実行を注意深く進めよう。すると図2.31のようなcall命令が見つかる。
write()の内部なので、かなり核心に近付いているように思われる。ここはstepiで関数の内部に入っていこう。
すると図2.32のようになった。「int」という、今まで出てきていない命令があるようだ。
メッセージが出力される瞬間
さてint $0x80という命令にたどりついたら、stepiでステップ実行してみよう。
すると、図2.34のようにしてメッセージが出力された。
つまり「int $0x80」という命令が実行された瞬間に、ハロー・ワールドのメッセージが出力されているということになる。
さて、これでメッセージ出力の核心まで進めることができた。ここで「where」というコマンドを実行してみよう。
(gdb) where #0 0x00110416 in __kernel_vsyscall () #1 0x08053d92 in __write_nocancel () #2 0x08067671 in _IO_new_file_write () #3 0x0806819b in _IO_new_do_write () #4 0x080683ea in _IO_new_file_overflow () #5 0x080673f4 in _IO_new_file_xsputn () #6 0x08059738 in vfprintf () #7 0x08049381 in printf () #8 0x080482e2 in main (argc=1, argv=0xbffffc14) at hello.c:5 (gdb)
whereは関数の呼び出し手順を遡って調べて表示するコマンドだ。関数呼び出しは実際には戻り先や関数内のさまざまなパラメータをスタック上に保存することで実現されており、このような逆算処理は一般にスタックのバックトレースと呼ばれる。
よってこれがmain()からprintf()が呼ばれ、実際にメッセージが出力されるまでの一連の関数の呼び出し手順になる。
なお図2.30を見ると、__write_nocancelの直前にwriteというシンボルがあり、write()の呼び出しではwriteの先頭部分が実行されていることに注目してほしい。つまりwhereでは__write_nocancelが表示されているのだが、これはおそらくその後の__kernel_vsyscall()の呼び出しが__write_nocancelの部分から行われているためGDBがそのように表示しているだけであり、実際には__write_nocancelではなくwriteが呼び出されているわけだ。
つまりprintf()の先では、最終的にはシステムコールのwrite()が呼ばれていることになる。
システムコールの呼び出し
ここでOSのシステムコールについて考えてみよう。
アプリケーション・プログラムとOSカーネルの間のインターフェースは、システムコールだ。アプリケーション・プログラムは、最終的にはシステムコールを呼び出すことになる。
CentOS環境ではopen()やread()といったシステムコールが利用できる。これらはUNIXライクなOSでのシステムコールAPIであり、POSIXという仕様で定義されている。
なおここで「Linux」と呼ばずに「CentOS環境」と言っているのは、Linuxが持っているのは「open」というシステムコールであり、「open()」というAPIを提供しているのはglibcであるためだ。
ハロー・ワールドのサンプル・プログラムからは、どのようなシステムコールが呼ばれているのだろうか。
straceによるトレース
CentOS環境ではstraceというコマンドで、プログラムが呼び出しているシステムコールをトレースすることができる。
ハロー・ワールドに対してstraceを試してみよう。
[user@localhost hello]$ strace ./hello execve("./hello", ["./hello"], [/* 26 vars */]) = 0 uname({sys="Linux", node="localhost.localdomain", ...}) = 0 brk(0) = 0x9e39000 brk(0x9e39cd0) = 0x9e39cd0 set_thread_area({entry_number:-1 -> 6, base_addr:0x9e39830, limit:1048575, seg_32bit:1 , contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 brk(0x9e5acd0) = 0x9e5acd0 brk(0x9e5b000) = 0x9e5b000 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb777f000 write(1, "Hello World! 1 ./hello\n", 23Hello World! 1 ./hello ) = 23 exit_group(0) = ? [user@localhost hello]$
いくつかのシステムコールが呼ばれていることがわかる。そして注目すべきは、最後のほうにあるwrite()の呼び出しだ。ここでwriteシステムコールによって、ハロー・ワールドのメッセージが出力されていることが確認できる。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- プログラミング言語Cについて知ろう
プログラミング言語の基本となる「C」。正しい文法や作法を身に付けよう。Cには確かに学ぶだけの価値がある(編集部) - シェルコード解析に必携の「5つ道具」
コンピュータウイルスの解析などに欠かせないリバースエンジニアリング技術ですが、何だか難しそうだな、という印象を抱いている人も多いのではないでしょうか。この連載では、「シェルコード」を例に、実践形式でその基礎を紹介していきます。(編集部) - 【 od 】コマンド――ファイルを8進数や16進数でダンプする
本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回は、「od」コマンドです。