アセンブリを書いてみる
アセンブリ言語はOSごとに異なっており、リファレンスも検索性が高いものがありません。
まともにリファレンスの情報をベースに実装していくのはとても困難な作業です。既存のまとめの継ぎ接ぎになってしまった感は否めなせんが、自分のメモとして残します。
主に参考にしたのは以下のサイトです。
環境
$ uname -a
Darwin user-pc.local 20.3.0 Darwin Kernel Version 20.3.0: Thu Jan 21 00:07:06 PST 2021; root:xnu-7195.81.3~1/RELEASE_X86_64 x86_64
$ sw_vers
ProductName: macOS
ProductVersion: 11.2.3
BuildVersion: 20D91
$ nasm -v
NASM version 2.15.05 compiled on Aug 29 2020
書いたコード
追記 2021/04/23 プロローグ、エピローグの処理を消去
section .text
global _ft_strlen
_ft_strlen
xor rax, rax
.loop:
cmp BYTE [rdi + rax], 0
je .end
inc rax
jmp .loop
.end:
ret
セクション
section .text
global _ft_strlen
引数の受け取り
引数の受け取りはレジスタを通して行われます
どのレジスタにどの引数が格納されるかは呼び出し規約というルールで定められているようです。
x86 calling conventions - Wikipedia
引数 | レジスタ |
---|---|
第1引数 | RDI |
第2引数 | RSI |
第3引数 | RDX |
第4引数 | RCX |
第5引数 | R8 |
今回はstrlenなので第1引数のみ使用しています。
xor rax, rax
raxというレジスタに0を入れています。
以下のように書くこともできますが、高速化のためにxorを使っています。
mov rax, 0x0
c++ - bitの初期化は「0代入」と「^(XOR)演算」どちらが速い? - スタック・オーバーフロー
やっていることとしては、int i = 0;
みたいなイメージです。
raxとは
汎用レジスタであり、64bitの値を扱うことが可能。
システムコール番号の指定にも使用され、結果が返る場所でもある。
変数的なものと思えばOKです。
cmp BYTE [rdi + rax], 0x0
cmp BYTE [rdi + rax], 0x0
je .end
rdi + rax のポインタからBYTE分(1byte分)、0x0と比較し、0x0と等しかった場合、_endにジャンプする。
大かっこでくくるとポインタを表すようになります。
BYTE
参照するアドレスの範囲を指定しています。
指定する範囲は以下のようになります。
指定 | バイト数
- | -
BYTE | 1 バイト
WORD | 2 バイト
DWORD | 4 バイト
FWORD | 6 バイト
QWORD | 8 バイト
cmp
レジスタとレジスタ、レジスタとメモリ内容、レジスタと定数を比較して、結果をフラグに反映させます。
内部的には左側のオペランドから右のオペランドを減算しますが、減算結果は返しません。
通常、条件ジャンプ命令が続きます。
je .end
真だったら(1だったら).endにジャンプ
条件ジャンプ命令
条件ジャンプ命令には様々な種類があります。
命令 | ジャンプ条件 |
---|---|
JA | より上(CF = 0 & ZF = 0) |
JAE | より上か等しい(CF = 0) |
JB | より下(CF = 1) |
JBE | より下か等しい(CF = 1 |
JC | キャリーがある(CF = 1) |
JCXZ | CXレジスタが0 |
JE | 等しい(ZF = 1) |
JG | より大きい(ZF = 0 & SF = OF) |
JGE | より大きいか等しい(SD = OF) |
JL | より小さい(SF ! OF) |
JLE | より小さいか等しい(ZF = 1 |
JNA | より上でない(CF = 1 |
JNAE | より上でなく等しい(CF = 1) |
JNB | より下でない(CF = 0) |
JNBE | より下でなく等しい(CF = 0 & ZF = 0) |
JNC | キャリーがない(CF = 0) |
JNE | 等しくない(ZF = 0) |
JNG | より大きくない(ZF = 1 |
JNGE | より大きくなく等しくない(SF ! OF) |
JNL | より小さくない(SF = OF) |
JNLE | より小さくなく等しくない(ZF = 0 & SF = OF) |
JNO | オーバーフローがない(OF = 0) |
JNP | パリティがない(PF = 0) |
JNS | 符号がない(SF = 0) |
JNZ | ゼロではない(ZF = 0) |
JO | オーバーフローがある(PF = 1) |
JP | パリティがある(PF = 1) |
JPE | パリティが偶数(PF = 1) |
JPO | パリティが基数(PF = 0) |
JS | 符合がある(SF = 1) |
JZ | ゼロである(ZF = 1) |
一覧は以下から引用しました。
インラインアセンブラで学ぶアセンブリ言語 第3回 (1/3):CodeZine(コードジン)
inc
inc rax
インクリメントです。
i++
と同じです。ここではraxの値をインクリメントしています。
jmp
jmp .loop
_loopにジャンプします。
ret
ret
retは pop rip
と同じ意味
呼び出し元に戻るということです。
参考
今回は使っていませんが、インテルのアセンブリに関しては以下の資料集が参考になリます。
リファンレスとしては以下のintelのものが最も適切かと思いますが、PDFのみで検索性も低く使いにくいです。
FreeBSDに固有な実装もあり、リファレンスを探しても見つからないものは以下のサイトを参照したりもしました。
Chapter 11. x86 Assembly Language Programming
ここはある程度検索できるので便利でした。
Microsoft マクロアセンブラーリファレンス | Microsoft Docs