writeシステムコールを直接呼び出し

今回第5回は、アセンブラに挑戦してみましょう。アセンブラでは、必然的にOSやCPUに依存したプログラムとなりますが、これは仕方ないところでしょう。

リスト1は、Linux(CPUはx86)版のアセンブラのHello Worldです。このプログラムは、C言語での「write(1, "Hello World\n", 12); _exit(0);」に相当します。アセンブラでは、ソフトウェア割り込み(int $0x80)でwriteシステムコールを直接呼び出してメッセージを表示しています。その後、プログラムを正常に終了するため、_exitシステムコールを呼び出して終了です。このように、Linuxのシステムコールでは、システムコール番号(eax)やその引数の値(ebx、ecx、edxなど)を、CPUレジスタに代入して渡します。

このプログラムは、実はもう少しコードサイズを最適化できるのですが、わかりやすさ優先で記述してあります。

リスト1 Linux(x86)のアセンブラ(as_write_linux.s)

        .text                     ← テキスト(プログラム)セクションの開始
_start:                           ← 実行開始アドレスのシンボル
        .globl  _start            ← _startをグローバルシンボルにする
        mov     $12, %edx         ← 出力バイト数、12バイトをedxに代入
        mov     $message, %ecx    ← 文字列の先頭アドレスをecxに代入
        mov     $1, %ebx          ← 標準出力のファイル記述子、1番をebxに代入
        mov     $4, %eax          ← writeのシステムコール番号、4番をeaxに代入
        int     $0x80             ← システムコール実行(ソフトウェア割り込み)

        xor     %ebx, %ebx        ← 終了ステータス、0をebxに代入
        mov     $1, %eax          ← _exitのシステムコール番号、1番をeaxに代入
        int     $0x80             ← システムコール実行(ソフトウェア割り込み)
message:                          ← 文字列の先頭アドレスのシンボル
        .ascii  "Hello World\n"   ← 文字列本体

アセンブルには、実行例1のように、gccコマンドがそのまま使えます。ファイルの拡張子が.sの場合、自動的にアセンブラのソースとみなされます。ここで、作成される実行バイナリファイルが、libcや、Cランタイムオブジェクトとリンクしないように、-nostdlibオプションを付けることが重要です。

実行例1 アセンブル方法とプログラムの実行

$ gcc -o as_write_linux as_write_linux.s -nostdlib -static  ← -nostdlibを付ける
$ ./as_write_linux           ← 作成された実行バイナリファイルを実行
Hello World                  ← 確かにHello Worldが表示される
$                            ← シェルのプロンプトに戻る

FreeBSDの場合

リスト2はFreeBSDの場合のアセンブラです。FreeBSDでは、システムコールがC言語の関数の形で呼び出されることを考慮して、引数をスタックにpushして渡す仕様になっています。この例のようにシステムコールを直接呼び出す場合、引数の後にダミーのリターンアドレスのpushが必要です。

このプログラムは短く、すぐに_exitで終了するため、システムコールの後のpopを行わず、スタックをもとに戻すことを省略しています。

リスト2 FreeBSD(x86)のアセンブラ(as_write_freebsd.s)

        .text
_start:
        .globl  _start
        push    $12              ← writeの第3引数(出力バイト数)をpush
        push    $message         ← writeの第2引数(文字列のアドレス)をpush
        push    $1               ← writeの第1引数(ファイル記述子)をpush
        push    $0               ← ダミーのリターンアドレスをpush
        mov     $4, %eax         ← writeのシステムコール番号、4番をeaxに代入
        int     $0x80            ← システムコール実行(ソフトウェア割り込み)

        push    $0               ← _exitの第1引数(終了ステータス)をpush
        push    $0               ← ダミーのリターンアドレスをpush
        mov     $1, %eax         ← _exitのシステムコール番号、1番をeaxに代入
        int     $0x80            ← システムコール実行(ソフトウェア割り込み)
message:
        .ascii  "Hello World\n"

Solaris(x86)の場合

x86版Solarisの場合は、ソフトウェア割り込みの番号が$0x80から$0x91に変わる以外は、FreeBSDと同じになります(リスト3)。

ただし、「int $0x91」はSolaris10のシステムコールです。Solaris9以前では、代わりに「lcall $0x27,$0」または「lcall $7,$0」という命令を使用する必要があります。

リスト3 Solaris10(x86)のアセンブラ(as_write_solaris10.s)

        .text
_start:
        .globl  _start
        push    $12
        push    $message
        push    $1
        push    $0
        mov     $4, %eax         ← writeのシステムコール番号
        int     $0x91            ← Solaris10(x86)のシステムコール

        push    $0
        push    $0
        mov     $1, %eax         ← _exitのシステムコール番号
        int     $0x91            ← Solaris10(x86)のシステムコール
message:
        .ascii  "Hello World\n"

SunOS4(SPARC)の場合

最後にx86以外のCPUの例として、SunOS4(CPUはSPARC)のアセンブラを挙げておきましょう(リスト4)。SunOS4では、システムコールは「ta 0」というトラップ命令を使用し、システムコール番号はg1に、その引数は順にo0、o2、o3などのレジスタに入れて渡します。なお、SPRACでは、レジスタへの32bit定数値の代入が1命令ではできず、sethiとorの2命令を使って行います。

リスト4 SunOS4(SPARC)のアセンブラ(as_write_sunos4.s)

        .text
_start:
        .globl  _start
        mov     12, %o2                ← 出力バイト数、12バイトをo2に代入
        sethi   %hi(message), %o1      ← 文字列のアドレスの上位ビットをo1に代入
        or      %o1, %lo(message), %o1 ← 文字列のアドレスの下位ビットをo1に代入
        mov     1, %o0                 ← ファイル記述子1番をo0に代入
        mov     4, %g1                 ← writeのシステムコール番号4番をg1に代入
        ta      0                      ← SunOS4のシステムコール(トラップ命令)

        clr     %o0                    ← 終了ステータス0をo0に代入
        mov     1, %g1                 ← _exitのシステムコール番号1番をg1に代入
        ta      0                      ← SunOS4のシステムコール(トラップ命令)

        .align  8                      ← 8バイト境界にそろえる
message:
        .ascii  "Hello World\n"        ← 文字列本体