1. アセンブリ言語によるプログラム
最初のプログラム
ここでは NASMというアセンブラで直接 Linux カーネルのシステムコールを 使うプログラムを作成していきます.
まず最初に Linux でアセンブラ(nasm) を使ったプログラムを紹介します. 例によって,hello, world を表示するものです.
;------------------------------------ ; hello.asm ;------------------------------------ section .text global _start msg db 'hello, world', 0x0A msglen equ $ - msg _start: mov ecx, msg mov edx, msglen mov eax, 4 mov ebx, 1 int 0x80 mov eax, 1 mov ebx, 0 int 0x80 ;------------------------------------
アセンブル,リンクは以下のコマンドで行います.
nasm -f elf hello.asm ld -s -o hello hello.o
./hello で実行できます.
同等のプログラムの C バージョンは
/* hello.c */ #include <stdio.h> int main(int argc, char* argv[]) { puts("hello, world\n"); return 0; }
gcc -o helloc hello.c でコンパイル. ./helloc で実行できます.
C のほうが分かり易いですよね.
面倒なアセンブラでプログラムを作成して何がウレシイのでしょうか? ではプログラムの大きさを比較してみましょう.
-rwxr-xr-x 1 jun users 452 Apr 5 00:13 hello -rwxr-xr-x 1 jun users 11309 Apr 5 00:17 helloc
コンパイラに最適化を指定してみます.
$ gcc -O2 -o helloc hello.c $ ls -l
-rwxr-xr-x 1 jun users 11277 Apr 5 00:17 helloc
ストリップして余計な情報をのぞくと
$ strip helloc $ ls -l helloc
-rwxr-xr-x 1 jun users 2912 Apr 5 00:18 helloc
かなり小さくなりました.
しかし,シェアドライブラリというものが存在します.
$ ldd helloc libc.so.6 => /lib/libc.so.6 (0x40017000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
/lib/libc.so.6 が何かを調べてみるとシンボリックリンクです.
$ ls -l /lib/libc.so.6
lrwxrwxrwx 1 root root 13 Mar 3 01:03 /lib/libc.so.6 -> libc-2.1.2.so*
シンボリックリンク先のファイルを調べます.
$ ls -l /lib/libc-2.1.2.so
-rwxr-xr-x 1 root root 4021488 Sep 8 1999 /lib/libc-2.1.2.so*
4Mバイトもある巨大なファイルが実行時に必要です.
シェアドライブラリを使用しないでライブラリの必要な部分だけをを実行ファイル に含めるような設定でコンパイルした場合には
$ gcc -O2 --static -o helloc hello.c $ ls -l helloc
-rwxr-xr-x 1 jun users 923630 Apr 5 00:18 helloc
ストリップすると
$ strip helloc $ ls -l helloc
-rwxr-xr-x 1 jun users 201348 Apr 5 00:18 helloc
200kバイトにもなりますが,次のようにシェアドライブラリは不要です.
$ ldd helloc not a dynamic executable
このスタティックリンク版の helloc の実行に必要なのは Linux カーネルのみです. ディストリビューションには関係無くどこでも実行できます.
この hello.c のようなごく小さいプログラム1つだけの場合には,スタティック リンクしたほうが小さくなりますが,多くのプログラムを使う場合(Linux の ディストリビューション全体のように) には,シェアドライブラリを使用する ダイナミックリンクが全体として圧倒的に小さくなります.
さて,452 バイトの hello は シェアドライブラリを使っているのでしょうか?
$ ldd hello not a dynamic executable
したがってアセンブラで作成した場合の実行プログラムのサイズは,C で作成した 場合の 1/450 になっています.
もう一度,hello.asm を見てみましょう.
;------------------------------------ ; hello.asm ;------------------------------------ section .text global _start msg db 'hello, world', 0x0A msglen equ $ - msg _start: mov ecx, msg ; 文字列の場所を指定 mov edx, msglen ; 文字列の長さを設定 mov eax, 4 ; 出力のシステムコール mov ebx, 1 ; 標準出力を指定 int 0x80 ; システムコール実行 mov eax, 1 ; 終了のシステムコール mov ebx, 0 ; 正常終了の 0 に設定 int 0x80 ; システムコール実行 ;------------------------------------
今回はこの例では, プログラムを終了させるシステムコールと文字列を表示 するシステムコールの2つを使っています.
eax にシステムコール番号を設定してソフトウェア割込み(int 0x80) を することで,カーネルの持つ強力な機能を使うことができます.
Linux上でアセンブラを使ってプログラムする場合,libc の関数を使用する 方法もありますが,ここではシステムコールを直接呼び出してプログラムする 方法をとります.
MS-DOSでアセンブラ(MASM, TASMなど)を使ったことがある人には簡単と思います. さらに MS-DOSの場合と大きくちがって,どんなプログラムを実行してもリブート する必要が無いことでしょう. 無限ループや暴走もカーネルで制御できます.
アセンブリ言語の欠点として
- わかりにくい.
- 大規模なプログラムが作成しにくい.
- メンテナンスが困難.
- 最近のコンパイラは,人がアセンブラで直接記述するより良いコードを生成する場合が多い (といわれる).
- 情報が少ない .
- 慣れるほど面倒になる.
一方,アセンブラを使う利点は
- CPU を支配できる.
- コンパイラより高速なコードを書ける (場合も実は多い).
- 文法が簡単である.
- バイナリ (実行できるプログラム) が小さい.
- まったく新しい OS を作るには必須の知識である.
- ウイルスに負けない体力を養える.
- コンパイラやインタプリタの作者になれる.
- Linux カーネルの機能を理解できる.
- C のポインタが簡単に理解できる.
- 人に自慢できる (かも ;-).
もし,興味がある場合,読み進んでください.