ソースファイルの分割
「EAXレジスタの値を10進数で出力する(前ページのprint_eax)」のような汎用的なサブルーチンは,いろいろなプログラムで使いたくなる。 しかし,print_eaxの中身をいろいろなプログラムにコピー & ペーストするのは,手間も掛かるし,同じもののコピーがたくさんできてディスク領域が無駄だ。さらに,print_eaxに修正の必要が生じると,それらのコピーをすべて修正しなければならない。
もっとよい方法は,print_eaxを独立したソースファイルに記述して,ほかのプログラムからそれを利用することだ。 以下は,プログラム本体を定義するソースファイル test_print.s と,print_eaxを定義するソースファイル print_eax.s の二つに分けてプログラムを記述した例だ。
test_print.s
section .text
global _start
extern print_eax ; 別ファイルのラベル
_start:
mov eax, 0xffff
call print_eax ; eaxの値を10進数で出力
mul eax ; eaxの2乗
call print_eax ; eaxの値を10進数で出力
mov eax, 1
mov ebx, 0
int 0x80 ; exit
print_eax.s
; eaxの値を10進数で標準出力に出力する.
; 汎用レジスタの値は保存される (破壊しない).
section .text
global print_eax ; 別ファイルから参照可能にする
print_eax:
-- print_eaxの本体 --
ret ; print_eaxの終端
-- 以降, print_eaxのためのデータ領域の定義など --
アセンブル手順は以下のようになる。
$ nasm test_print.s -- test_print.oを生成
$ nasm print_eax.s -- print_eax.oを生成
$ ld test_print.o print_eax.o -- 結合してa.outを生成
$ ./a.out
65535
4294836225
アセンブル結果であるオブジェクトファイル(.o ファイル)は,複数を結合 (link) することが可能だ。上記の例の場合,test_print.s にはprint_eaxの定義がなく,print_eax.s には実行開始点 (_start) がなく,どちらも単独では不完全だが,それぞれアセンブルしてオブジェクトファイルを生成することは支障なくできる。 これらのオブジェクトファイルをldコマンドに入力すると,.textセクション同士や.dataセクション同士が一つにまとめられ,test_print.o では未確定だったラベルの値(print_eaxの番地)が確定されて,完成した実行可能ファイルが出来上がる。
複数のソースファイルに分割してプログラムを記述する際のポイントは以下の2点だ。
- 別ファイルで使うラベルは「global宣言」する。
- 別ファイルのラベルを使う場合は「extern宣言」する。
例えば上記の例で,ラベル print_eax は print_eax.s でglobal宣言され,test_print.s でextern宣言されている。つまり,ラベルを提供する側でglobal宣言(「このラベルは皆で使うよ」),ラベルを使う側でextern宣言(「このラベルはこのファイルの外で定義されてるよ」)する。test_print.s 中のextern宣言がないと,アセンブル時に「print_eaxが未定義」というエラーになる。print_eax.s 中のglobal宣言がないと,リンク時に「print_eaxが未定義」というエラーになる。