こんにちわ、Unityエンジニアのオオバです。

ECS完全に理解した勉強会で、
@tnayukiさんのLTでアセンブリが話題になりました。

良いプログラマーになるための近道は
アセンブリを勉強することだと
プログラマ初心者にアドバイスしたこと。

オオバも可能であれば良いプログラマになりたいので、
アセンブリを始めました。

※厳密には`64bit macOS環境`におけるアセンブリの話

ILの勉強もはじめました。
興味ある方はどうぞ読んでみてください。

  1. nasmをインストール
  2. アセンブリ用意
  3. コンパイル
この記事の内容

1. ググるとnasmが出てくる

ということで、nasmをインストールします。

$ brew install nasm  

HomeBrewでnasmはインストール可能です。

2.Helloworld出力のソースコードを用意

Hello Worldを標準出力するアセンブリソースコードをhelloworld.asmというファイル名で保存。

helloworld.asm · GitHub

3.コンパイル(アセンブル)

ソースコードhelloworld.asm
機械語(バイナリ)に変換します。

$ nasm -f macho64 -o helloworld.o helloworld.asm  

機械語になったhelloworld.o(バイナリファイル)が出力されます。.oファイルはオブジェクトファイルと呼ぶらしい。

4.実行ファイルの作成

ldコマンド(リンカコマンド)を使って実行ファイル(バイナリ)を生成します。

ld -arch x86_64 -macosx_version_min 10.11 helloworld.o -lSystem  

もし以下のような感じでXcode周りのエラーが出てうまくいかない場合は、

xcrun: error: active developer path ("/Applications/Xcode9.3.app/Contents/Developer") does not exist
Use sudo xcode-select --switch path/to/Xcode.app to specify the Xcode that you wish to use for command line developer tools, or use xcode-select --install to install the standalone command line developer tools.

こちらのコマンドを実行してXcodeのコマンドラインツールをインストールします。

xcode-select --install  

この記事でも起きたコマンドラインに関するエラーと同じ対処法です。
PyenvでPython3.6.3インストール時のトラブル

改めて、先程のldコマンドを実行します。

ld: warning: PIE disabled. Absolute addressing (perhaps -mdynamic-no-pic) not allowed in code signed PIE, but used in _main from helloworld.o. To fix this warning, don't compile with -mdynamic-no-pic or link with -Wl,-no_pie

するとこのようなwarningが出てしまいますが、a.out(実行バイナリ)が出力されます。

5.Hello World!

以下のようにa.outを実行します。

./a.out  

【初心者向け】Mac環境でアセンブリの始め方_0

するとコンソールに出ました!Hello Worldです。

ここからアセンブリのソースコードについて調べていきます。

global _main  

_.から始まるものをシンボルと呼びます。
globalがくっついていると、外部参照を可能にするとのことです。
また複数同名のシンボル名は定義できません。

; DATAセクションの始まりを定義  
section .data  

セクションは、以下のために存在するとのことです。

プログラム本体やプログラム中で使用する定数、文字列、変数 に必要な性質に応じて区別して管理するため

TEXTセクション書き換え不可、DATAセクションは書き換え可能領域とのことですが、今回はあまり深くは踏み込まないことにします。

str_hello:   db  Hello World", 0x0a  

str_helloというラベルを定義しています。あとからラベル内から参照します。

dbとは、db 文字列と書くと1文字ずつASCIIコードに変換し、数値の連続として書き込みます。
db 数字A, 数字B, 数字Cとすると数字A〜Cを数値の連続として書き込みます。連続した値はカンマで区切ります。
dbは、要は値を書き込みます。

その書き込んだものをstr_helloという任意の名前のラベルで参照できるようにしているということです。

_main:  

._から始まり、:で終わるシンボルをラベルと呼び、この例では_main:と定義した場所のメモリアドレスを値として持ちます。

ラベル以下のコードがそのラベルの処理内容です。

    mov rax, 0x2000004  
    mov rdi, 1  
    mov rsi, str_hello  
    mov rdx, 13  
    syscall  
    mov rax, 0x2000001  
    mov rdi, 0  
    syscall  

文法的にはこんな感じ。

今回出てくるオペコードは movのみです。
またmov, rax, rdiなどをまとめて、ニーモニックと呼ばれます。

movは値をコピーするオペコードで、

mov rax, 0x2000004  

rax0x2000004という値をコピーするという意味になります。

rax部分がレジスタというものにあたり、今回4種類のレジスタが出てきたので以下まとめています。

使用用途
--- | --- | ---
rax | アキュムレーターレジスタ | 計算時の変数、関数戻り値格納システムコール番号指定
rdi | ディスティネーションインデックスレジスタ | スタックの先頭アドレスを保持
rsi | ソースインデックスレジスタ | 文字列操作時のソースポインタ
rdx | データレジスタ | 計算時の変数に使用され、I/Oポインタとして使用

20行目 mov rax, 0x2000004

rax(アキュムレーターレジスタ)に標準出力システムコールの番号4をコピーしています。
x64環境では0x2000000を加算した値を入れます。

24行目 mov rdi, 1

rdi(ディスティネーションインデックスレジスタ)に標準出力システムコールの第1引数になる値をコピーします。
0 : 標準入力、1 : 標準出力, 2 : 標準エラーとのこと

今回は標準出力させるの1を指定します。

29行目 mov rsi, str_hello

rsi(ソースインデックスレジスタ)に標準出力システムコールの第2引数になる値をコピーします。
これは、事前に定義しているstr_helloと記述することでその場所のメモリアドレスがコピーされます。

34行目 mov rdx, 13

rdx(データレジスタ)に標準出力システムコールの第3引数になる値をコピーします。
出力するデータのバイト数を指定します。
今回1文字1バイト使用するため、例えばmov rdx, 1とすると、Hしか出力されません。

37行目 syscall

標準出力システムコール実行
Hello Worldとコンソール上に出力されます。

41行目 mov rax, 0x2000001

exitシステムコール 1を指定(現在のプロセスを終了)

44行目 mov rdi, 0

exitシステムコールの第1引数になる値をコピーします。
ステータスコード0を指定。

48行目 syscall

exitシステムコールの実行。

といった処理内容になります。

まとめ

全く事前知識0の状態でアセンブリに触れてみましたが、そんなに難しくはなく、ある程度ニーモニックの意味が分かれば、なんとなく読める気がしてきました(書ける気はしない)。

勉強会でも紹介されていましたが、Unityのバーストコンパイラではアセンブリソースコードが表示することができます。どのようなコードに最適化がされているか、ニヤニヤしながら眺められるようになると、新しい自分を見つけられるかもしれません。

LTで分かる!アセンブラ
@tnayukiさんのスライドがアップされていたので、追加しました。

オススメ記事
検証環境
参考サイト