私がプログラムに触れ始めた頃には、家にパソコンがあったし、既にインターネットがあった。GCCもGNU/Linuxも既にあったし、その頃はあまりわかっていなかったけれど、Javaとオブジェクト指向が人気だったように思う。コンパイルやビルドの仕組みもある程度は整備されていたように思う。とあるきっかけで知人に勧められたVineLinuxのインストーラをCDに焼いて、雑にインストールしたVineLinuxを使ったりした記憶がある。もう、本当にそうだったのかあまり記憶にないけれど。
C言語の入門書を開いて「Hello world!」を表示する簡単なプログラムを書いた。
#include <stdio.h>
int main () {
printf("hello world!\n");
}
おそらく既に入っいていたか、何らかの方法でインストールしたGCCを使い、書いたプログラムを実行可能な形式のファイルにビルドした。
gcc main.c
ソースコードは変換され、実行可能なファイルが a.out
という名前で作成された。このファイルを実行すると画面にhello world!という文章を見ることができた。
./a.out
hello world!
難しくないんだなという印象とともに、だから何なんだろうという気にもなった。この文字が表示できたから一体何になるというのか、何の役に立つのかわからなかった。画面に表示されている「hello world!」という文字の延長線上に、誰かの役に立つような便利なプログラムがあるようにも思えなかった。それから暫くして、プログラムを書く事が僕の仕事になった。何で動いているのかよくわからないものを、その使い方だけを覚えて、目的を達成するという事をするようになった。時折今でもあの時の「hello world!」の文字と、いたたまれない気持ちが頭をちらつく。できる事は増えたけれど、よくわかっていないという事は変わっていない。何で動いているのかを理解していく事は、結構大変な作業で、ある程度の所で折り合いを付けて、"これはこんなふうに動くもの"という認識を前提にして進んでいく方が効率が良いし、それ以上掘り下げる必要に迫られる事も少ない。掘り下げる所とそれを諦める所の境界を、どのように決めるのかと聞かれると結構困る。ある人はライブラリやフレームワークの使い方さえ知っていれば十分だというかもしれないし、アルゴリズムについて理解を深めた方が良いという人がいるかもしれなし、そんな事はどうでもよくて、どこにどのような形式でデータが保持されるかを理解するべきだという人もいそうだ。たぶん正解はないのだろう。正解は自分で決める必要がある。
私にとってよくわかっていない事は本当に沢山あるのだけれど、その中の1つにコンパイラがある。コンパイラの動作原理が理解できていないとか、そんな難しい話じゃなくて、コンパイラがどんな順序を踏んで処理を行っているのかという簡単な事さえわかっていない。「コンパイルしてオブジェクトコードになった後、リンカーによってリンクされ、実行可能なファイルになるんだよ」という雑な知識があるだけだ。今の時代、大抵それでも困らない。それでも、どこかモヤモヤする。今回はそんなモヤモヤを解消するために、ソースから実行可能ファイルを生成する事を、段階を追って行ってみたいと思う。
最小のプログラム
最小のプログラムをC言語で書いてみる。「hello world!」を画面に表示するなんてケチ臭い事は言わない。何もしないプログラムを書く。ヘッダをインクルードするような所謂"オマジナイ"も必要ない1。
環境
環境はmacOS、ビルドにはHomebrewでインストールしたGCCを使う事にする。そのためgcc-12というコマンドになっている。使用バージョンがわかりやすくなるため、このままの名前とする。ただし、GCCはmacOS用のリンカーをサポートしないため、ldコマンドについては標準の/usr/bin/ldを使用する。
ビルドして実行を確認する
まずビルド2してみる。
gcc-12 small.c
ビルドに成功すると a.out
というファイルが生成される。このファイルを実行する。
./a.out
実行すると何もせずプログラムは終了する。何かを画面へ表示する事もしない。
プリプロセス
gcc-12 -E small.c
small.iが生成される。
狭義のコンパイル
C言語のコードをコンパイルしアセンブリ言語へと変換する。
gcc-12 -S small.i
small.sが作成される。
アセンブル - アセンブリコードを機械語に変換する
アセンブリ言語に変換したファイルを元にオブジェクトファイルを生成する。
gcc-12 -c small.s
small.oが作成される。このファイルはバイナリファイルである為、そのままではここに掲載できない。hexdumpで表示すると次のようになる。
hexdump small.o
0000000 facf feed 0007 0100 0003 0000 0001 0000 0000010 0004 0000 01b8 0000 2000 0000 0000 0000 0000020 0019 0000 0138 0000 0000 0000 0000 0000 0000030 0000 0000 0000 0000 0000 0000 0000 0000 0000040 0068 0000 0000 0000 01d8 0000 0000 0000 0000050 0068 0000 0000 0000 0007 0000 0007 0000 0000060 0003 0000 0000 0000 5f5f 6574 7478 0000 0000070 0000 0000 0000 0000 5f5f 4554 5458 0000 0000080 0000 0000 0000 0000 0000 0000 0000 0000 0000090 0008 0000 0000 0000 01d8 0000 0004 0000 00000a0 0000 0000 0000 0000 0400 8000 0000 0000 00000b0 0000 0000 0000 0000 5f5f 6f63 706d 6361 00000c0 5f74 6e75 6977 646e 5f5f 444c 0000 0000 00000d0 0000 0000 0000 0000 0008 0000 0000 0000 00000e0 0020 0000 0000 0000 01e0 0000 0003 0000 00000f0 0240 0000 0001 0000 0000 0200 0000 0000 0000100 0000 0000 0000 0000 5f5f 6865 665f 6172 0000110 656d 0000 0000 0000 5f5f 4554 5458 0000 0000120 0000 0000 0000 0000 0028 0000 0000 0000 0000130 0040 0000 0000 0000 0200 0000 0003 0000 0000140 0000 0000 0000 0000 000b 6800 0000 0000 0000150 0000 0000 0000 0000 0032 0000 0018 0000 0000160 0001 0000 0000 000c 0300 000c 0000 0000 0000170 0002 0000 0018 0000 0248 0000 0001 0000 0000180 0258 0000 0008 0000 000b 0000 0050 0000 0000190 0000 0000 0000 0000 0000 0000 0001 0000 00001a0 0001 0000 0000 0000 0000 0000 0000 0000 00001b0 0000 0000 0000 0000 0000 0000 0000 0000 * 00001d0 0000 0000 0000 0000 4855 e589 c031 c35d 00001e0 0000 0000 0000 0000 0008 0000 0000 0100 00001f0 0000 0000 0000 0000 0000 0000 0000 0000 0000200 0014 0000 0000 0000 7a01 0052 7801 0110 0000210 0c10 0807 0190 0000 0024 0000 001c 0000 0000220 ffb8 ffff ffff ffff 0008 0000 0000 0000 0000230 4100 100e 0286 0d43 0006 0000 0000 0000 0000240 0000 0000 0001 0600 0001 0000 010f 0000 0000250 0000 0000 0000 0000 5f00 616d 6e69 0000 0000260
オブジェクトファイルの中身を確認したい場合には、objdumpコマンドを用いる事もできる。
objdump -D small.o
small.o: file format mach-o 64-bit x86-64 Disassembly of section __TEXT,__text: 0000000000000000 <_main>: 0: 55 pushq %rbp 1: 48 89 e5 movq %rsp, %rbp 4: 31 c0 xorl %eax, %eax 6: 5d popq %rbp 7: c3 retq Disassembly of section __LD,__compact_unwind: 0000000000000008 <__compact_unwind>: ... 10: 08 00 orb %al, (%rax) 12: 00 00 addb %al, (%rax) 14: 00 00 addb %al, (%rax) 16: 00 01 addb %al, (%rcx) ... Disassembly of section __TEXT,__eh_frame: 0000000000000028 <__eh_frame>: 28: 14 00 adcb $0, %al 2a: 00 00 addb %al, (%rax) 2c: 00 00 addb %al, (%rax) 2e: 00 00 addb %al, (%rax) 30: 01 7a 52 addl %edi, 82(%rdx) 33: 00 01 addb %al, (%rcx) 35: 78 10 js 0x47 <__eh_frame+0x1f> 37: 01 10 addl %edx, (%rax) 39: 0c 07 orb $7, %al 3b: 08 90 01 00 00 24 orb %dl, 603979777(%rax) 41: 00 00 addb %al, (%rax) 43: 00 1c 00 addb %bl, (%rax,%rax) 46: 00 00 addb %al, (%rax) 48: b8 ff ff ff ff movl $4294967295, %eax ## imm = 0xFFFFFFFF 4d: ff ff <unknown> 4f: ff 08 decl (%rax) ... 59: 41 0e <unknown> 5b: 10 86 02 43 0d 06 adcb %al, 101532418(%rsi) 61: 00 00 addb %al, (%rax) 63: 00 00 addb %al, (%rax) 65: 00 00 addb %al, (%rax) 67: 00 <unknown>
リンク
オブジェクトファイルをリンクする。手作業でリンクをする場合、オプションを指定する必要がある。指定しないとリンクエラーとなる。
ld small.o
ld: dynamic main executables must link with libSystem.dylib for architecture x86_64
必要なオプションを指定し、再度リンクを行う。
ld -lSystem -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk small.o
成功すると特に何も表示されずa.outというファイルが生成される。
実行する
生成されたa.outは実行可能ファイルの形式になっている為、実行できる。
./a.out
ただしこのプログラムは何もしないプログラムのため、何事もなかったかのように終了する。