アセンブリで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

f:id:kisaragi211:20200410165345p:plain

まとめ

アセンブリでHelloWorldすることができました。
その他のシステムコールにも挑戦しようと思います。

参考

https://www.ohmsha.co.jp/book/9784274222740/