アセンブリでHelloWorldする
環境
Ubuntu 18.04.4 LTS
NASM version 2.13.02
GNU ld (GNU Binutils for Ubuntu) 2.30
目標
アセンブリでシステムコールを呼び出し、HelloWorldを出力することを目指します。
基礎知識
今回HelloWorldをするにあたってwriteシステムコールを使って標準出力をしていきます。
システムコールとは
カーネル(OSの中核)が提供する処理をユーザが呼び出すこと。
例:writeやreadを使ったファイルへの読み書きなど。
カーネルのプロセスはカーネル空間という場所にあり、ユーザは通常そこへはアクセスできません。
そのためシステムコールを経由してカーネルの機能を利用します。
システムコールを呼び出すには
システムコールをアセンブリから呼び出すには「システムコールの識別子」「引数」の設定が必要です。
またそれぞれを設定する際に使うレジスタが決まっています。
設定項目 | レジスタ名 |
---|---|
識別子 | rax |
第1引数 | rdi |
第2引数 | rsi |
第3引数 | rdx |
第4引数 | r10 |
第5引数 | r8 |
第6引数 | r9 |
第7引数以降はスタックに積まれます。
上記の表を見ながら対応するレジスタに必要な値を設定していきます。
設定項目の調査
識別子の確認
raxレジスタに設定する識別子を探します。
この識別子を用いてシステムコールを呼び出す際、どの処理を行うのかを明示します。
今回はwriteシステムコールの識別子を調べます。
識別子は /usr/include/x86_64-linux-gnu/asm/unistd_64.h
にまとめて書いてあります。
その中身を見るなりgrepするなりしてwriteの行を探します。
grep write /usr/include/x86_64-linux-gnu/asm/unistd_64.h
うまく見つからない場合は grep -r write /usr/include/
等を実行すると出力される中に該当の行があると思います。
上記のようなコマンドを実行すると#define __NR_write 1
という行を見つけると思います。
この1というのがシステムコールの識別子です。
この値をraxに設定します。
引数の確認
man 2 write
をするとwriteシステムコールの使い方が確認できます。
manの2というオプションはシステムコールについてのマニュアルを表示するというオプションです。
ssize_t write(int fd, const void *buf, size_t count);
上記の形式にしたがって引数を設定します。
rax = 1 rdi = 1 rsi = "Hello Assembly\n" rdx = 15
今回は標準出力を使うのでfd(ファイルディスクリプション)は1を設定します。
コードを書く
ファイル名はhelloworld.asmにします。
section .data str: db "Hello Assembly", 0x0a section .text global _start _start: mov rax, 1 mov rdi, 1 mov rsi, str mov rdx, 15 syscall mov rax, 60 mov rdi, 0 syscall
それぞれの処理について説明していきます。
文字列の定義
section .data str: db "Hello Assembly", 0x0a
ここでは文字列の定義を行っています。
dataセグメントでは初期値をもつ変数を格納します。
なお初期値のないものはbssセグメントとなります。
c言語で書くとchar str[] = "Hello Assembly\n";
となります。
最後の0x0aというのは改行のアスキーコードです。
命令の記述
section .text global _start _start: mov rax, 1 mov rdi, 1 mov rsi, str mov rdx, 15 syscall
テキストセグメントでは処理する命令を記述します。
global _start
は_start
ラベルへ移動しそこから処理を勧めていくことを記述しています。
_start
ラベル以降に書いてある処理は先程調べた内容(識別子・引数)を設定しています。
rsi(第2引数)ではデータセグメントで定義したstrの場所を指定しています。
rdx(第3引数)は文字列の長さを設定しています。
c言語で書くと
char str[] = "Hello Assembly\n"; write(1, str, 15);
となります。
識別子と引数を設定してsyscallすると該当のシステムコールが呼び出されます。
プログラムの終了
mov rax, 60 mov rdi, 0 syscall
2つ目のシステムコールはexitシステムコールの呼び出しになります。
ステーテスコード0でexitを実行しプログラムを終了します。
実行
nasm -f elf64 -o helloworld.o helloworld.asm ld helloworld.o -o helloworld ./helloworld
まとめ
アセンブリでHelloWorldすることができました。
その他のシステムコールにも挑戦しようと思います。