アセンブリ言語に入門しよう

MMM Corporation
mmmuser

MMMバックエンドエンジニアの柳沼です。
いよいよ今週はJapan Container Daysですね!!

今回は、Hello Worldを出力してみることで、
アセンブリ言語の基礎を学んでみようと思います。

環境

今回はアセンブラにnasmを採用します。
バージョンは2.0.0以上であることを推奨します。

筆者の環境は以下のとおりです。

$ nasm --version
NASM version 2.13.01 compiled on Feb 28 2018

また、筆者のマシンのCPU情報は以下のとおりです。(4コアなため4行表示されています。)

$ cat /proc/cpuinfo | grep 'model name'
model name      : Intel(R) Core(TM) i3-6006U CPU @ 2.00GHz
model name      : Intel(R) Core(TM) i3-6006U CPU @ 2.00GHz
model name      : Intel(R) Core(TM) i3-6006U CPU @ 2.00GHz
model name      : Intel(R) Core(TM) i3-6006U CPU @ 2.00GHz

LinuxディストリビューションはGentooを使用しています。(darwinでもたぶんできると思うので頑張ってください。)
ディストリ及びカーネルのバージョンは以下のとおりです。

$ cat /etc/gentoo-release
Gentoo Base System release 2.4.1

$ uname -r
4.14.8-gentoo-r1

セクション

本エントリではアセンブリの全ての文法には触れません。
とりあえずHello Worldの出力に必要な知識に絞って説明していきます。

今回は、 データセクションテキストセクション について説明します。

データセクション

データセクションは、データを格納するために使用します。以下のような書き方をします。

section .data
    msg db      "Hello world!"

msg は変数名、 "Hello world!" がデータです。
db は、1バイト分のメモリを確保することを意味します。(dwは2バイト、ddは4バイト)

つまり、上記は

char msg[] = "Hello, world!"

と同じようなことです。

テキストセクション

テキストセクションには、実際の処理を記述します。
テキストセクションは、以下のように開始します。

section .text
    global _start

global _start は、プログラムのエントリーポイントになります。
_start はラベル(関数の開始位置みたいなもの)として使用します。

処理の文法は以下のとおりです。

[label:] instruction [operands] [; comment]

例えば、

mov     rax, 1

と書けば、 rax レジスタに 1 をセットする、という意味になります。

レジスタ

レジスタとは要するに記憶領域です。
メモリのように、データを保存することが可能です。
メモリよりも高速に動作し、CPUの上(物理)にあります。
本エントリで登場するレジスタは、x64環境のものであることに留意してください。(x86とかだと名前が違います。)

レジスタは(物理的に)複数存在しており、それぞれ名前がついています。
今回使用するのは以下のレジスタです。

  • rax : システムコール番号を保存する
  • rdi : 第1引数を保存する
  • rsi : 第2引数(のポインタ)を保存する
  • rdx : 第3引数を保存する

とりあえずこれだけわかれば、Hello Worldはできます。コードを見てみましょう。

アセンブリでHello World

以下のようなコードになります。

section .data
    msg db      "Hello world!"

section .text
    global _start
_start:
    mov     rax, 1
    mov     rdi, 1
    mov     rsi, msg
    mov     rdx, 12
    syscall
    mov    rax, 60
    mov    rdi, 0
    syscall

データセクションでは文字列を宣言しているだけです。
_start を読んでいきます。

mov     rax, 1

は、raxに1を入れています。
システムコール番号1は sys_write ですね。
システムコール番号はLinuxカーネルのsyscall_64.tblから参照できます。(便利)

mov     rdi, 1
mov     rsi, msg
mov     rdx, 12

については、前述の通り、引数を詰めています。
sys_write の引数を知るために、manを見てみましょう。 (man 2 write)

WRITE(2)                             Linux Programmer's Manual                            WRITE(2)

NAME
       write - write to a file descriptor

SYNOPSIS
       #include <unistd.h>

       ssize_t write(int fd, const void *buf, size_t count);

引数が3つ必要なことがわかります。
fd はファイルディスクリプタです。標準出力したいので、1をセットします。
buf には文字列を指定します。
count には、データのサイズを指定します。

syscall

上記で、今のレジスタの状態を基にシステムコールを発行します。
つまり、上記3つの引数を基に、raxレジスタに詰めているシステムコール(sys_write)を発行してくれます。

mov    rax, 60
mov    rdi, 0
syscall

こちらは、

exit(0)

を行っています。
システムコールの60番はexitで、引数は終了コードになります。

実行は、以下のように行います。

$ nasm -f elf64 -o hello.o hello.asm # オブジェクトファイル生成
$ ld -o hello hello.o # 実行バイナリ生成
$ ./hello # 実行

無事 Hello World! が表示されれば勝ちです。やりましたね。

まとめ

Hello Worldを通して、レジスタの使い方、システムコール発行の流れを追ってみました。
システムコールについては、こういうサイトを使えば
使うべきレジスタも教えてくれるので便利です。

アセンブリがマストなビジネスについてもMMMにご相談ください!

AUTHOR
デロイト トーマツ ウェブサービス株式会社(DWS)
デロイト トーマツ ウェブサービス株式会社(DWS)
デロイト トーマツ ウェブサービス株式会社はアマゾン ウェブ サービス(AWS)に 専門性や実績を認定された公式パートナーです。
記事URLをコピーしました