渋谷ほととぎす通信

完全趣味でやってるUnityメモ。説明できないところを説明できるようにするための個人ブログ。昨日の自分より少しでも大きくなれるように。。。 ※所属団体とは一切関係がありません

初心者向け事前知識無しの状態から、Mac環境でUnityエンジニアがアセンブリに触れてみる


ECS完全に理解した勉強会で、@tnayukiさんのLTでアセンブリが話題になりました。良いプログラマーになるための近道はアセンブリを勉強することだとプログラマ初心者にアドバイスしたという発表が端を発したような気がしています。

ということで、僕も可能であれば良いプログラマになりたいので、アセンブリに触れてみようと思います。
※僕はMacユーザーなので、Mac環境での話になります。
厳密には64bit macOS環境におけるアセンブリの話になります。

ILの勉強もはじめました。

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

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

brew install nasm

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

Hello Worldを標準出力するアセンブリソースコードをhelloworld.asmというファイル名で保存。
※コメントに各処理の内容をできる限り書き込んでおります

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

※インストールが終了したら、一旦Xcodeを立ち上げてダイアログに対してOKを選択する必要がありました。


この記事でも起きたコマンドラインに関するエラーと同じ対処法です。

改めて、先程の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

f:id:esakun:20181023150253p:plain:h60
するとコンソールに出ました!Hello Worldです。



ここからアセンブリのソースコードについて調べていきます。
※基本は先のソースコード内のコメントアウトを参照

global _main

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


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

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

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

Assembly Programming on ARM Linux(07)より

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のバーストコンパイラではアセンブリソースコードが表示することができます。どのようなコードに最適化がされているか、ニヤニヤしながら眺められるようになると、新しい自分を見つけられるかもしれません。


@tnayukiさんのスライドがアップされていたので、追加しました。

環境

  • macOS High Sierra 10.13.6
  • NASM version 2.13.03 compiled on Feb 8 2018

参考