2017年12月1日 | ブログ記事

はろーわーるどとお友達になろう

yasu

はじめに

こんにちは。Advent Calender 12月01日担当のyasuです。世界一有名なプログラムと言われているHello world。今回は、C言語のHello worldとのお遊びを紹介したいと思います。

Hello worldとは

まず、Hello worldとは、画面にHello Worldの文字列を出力するプログラムのことを指します。プログラムを使うほとんどの人が一度は書いたことがあるプログラムだと思います。ちなみに、C言語のソースコードはこんな感じです。

hello.c#include <stdio.h>

int main(int argc, char *argv[])
{
  printf("Hello World! %d %s\n", argc, argv[0]);
  return 0;
}

Hello worldと遊んでみよう

今回、GdbとGdbserverを使うので、まずはそのインストールをします。以下のコマンドでインストールしてください。

yum -y install gdb
yum -y install gdb-gdbserver

次に、hello.cをコンパイルします。これで新たにファイルが生成されます(私の場合はhelloというファイルでした)。

これで準備完了です!
次に、Gdbでhelloを起動します。その時に、gdbserverを用いて、リモートデバックをします。これにより、文字化けを防ぐことができます。
まずは、gdbserverを起動します。hello.cのあるディレクトリに移り、以下のコマンドを入力してください。

gdbserver localhost:12345 ./hello

localhostの後の数字は何でもよいです。

次に、別のターミナルを起動して、同じようにhello.cのあるディレクトリに移動して、以下のコマンドを入力します。

gdb -q hello

これで、gdbが起動しました。次は、gdbserverと接続をします。以下のコマンドを入力して、gdbserverと接続をします。

target extended-remote localhost:12345

実際やってみた画像はこんな感じです。

2017-12-01--3-
gdb側で実行したプログラムがgdbserver側で出力されているのが分かります。

それでは、準備が終わりましたので実際に遊んでみましょう。細かいGdbの使い方はここを参照してください。
今回は、Hello worldが出力される瞬間を調べてみます。

まずは、main関数にブレークポイントを張ります。

2017-12-01--4-

上の画像でcall命令を実行したら、Hello worldが出力されるので、今度はprintfにブレークポイントを張り、printfの内部動作を追っていきます。これを5000兆回くらいくりかえすとやがてwrite関数にたどり着くきます。ここでもcall命令があるところまでプログラムを進めていきます。

2017-12-01--5-

stepiを実行して関数の内部に入ってみます。

2017-12-01--6-

この命令を実行するとHello worldが出力されます。

2017-12-01--7-

よって、Hello worldはこのint命令が実行されたら出力されていることが分かります。ちなみにこのint命令はシステムコールと呼ばれるもので、この命令によってプログラムがOSの機能を呼び出しています。int命令までに呼び出した関数はこんな感じです。めちゃめちゃ多いですね。

2017-12-01--9-

今回は、システムコールの呼び出しまでの流れを追っていっただけで特に各関数での処理とかは見てないのですが、gdbにはレジスタの値を確認できたりもするので、そこに注目しながらデバックをしていっても面白いと思います。さすがにこれだけで終わるのも忍びないので、最後にシステムコールが呼ばれる直前のレジスタの値だけ見てみます。

eax    	       0x4	4
ecx    	       0xb77a3000	-1216729088
edx    	       0x17	23
ebx    	       0x1	1
esp    	       0xbfb2bdd8	0xbfb2bdd8
ebp    	       0xbfb2bdfc	0xbfb2bdfc
esi    	       0xb77a3000	-1216729088
edi    	       0x80d68c0        135096512
eip    	       0x171414	0x171414 <__kernel_vsyscall>
eflags 	       0x200246	[ PF ZF	IF ID ]
cs     	       0x73	115
ss     	       0x7b	123
ds     	       0x7b	123
es     	       0x7b	123
fs     	       0x0	0
gs     	       0x33	51

この中で、システムコールの引数に用いられるのはeax,ebx, ecx, edxの四つの値で、それぞれの値とその意味をまとめると次のような図になります。

レジスタ 意味
EAX 4 システムコール番号(write()は4)
EBX 1 出力先のファイルディスクリプタ
ECX 0xb77a3000 出力データのアドレス
EDX be 出力データのサイズ

つまり、EAXにシステムコール番号、EBX以降に引数を設定して、システムコールを発行して、Linuxカーネルに処理をさせていることがわかりました。

最後に

スクショの時間が投稿日になっているのはPCの設定ミスです。別に早朝急いで仕上げたわけじゃないんだからねっ!! 来年も書く機会があればもう少し準備したいと思います。勉強したてのことを記事にしたので間違っているところがあればご指摘お願いいたします。皆さんもぜひハローワールドと遊んでみてください。特に着地点のない話になってしまいましたが、この辺で終わりにしたいと思います。明日は、RLookさんとMENTOSさんの記事です。お楽しみに!

この記事を書いた人
yasu

traP17

この記事をシェア

このエントリーをはてなブックマークに追加

関連する記事

2017年12月27日
Splatoon2~ボムの使い方~
shigurure
2017年12月26日
RustでMCMC(Metropolis-Hasting)
David
2017年12月26日
NinjaFlickerが完成しました
gotoh
2017年12月25日
Project Obelisk [traP Advent Calendar 2017]
nari
2017年12月25日
tobiom
2017年12月25日
ネタがないので迷路を自動生成してみた
tsukatomo

活動の紹介

カテゴリ

タグ