x86-64 命令一覧
概要と記号
add5.c
をgcc -S add5.c
でコンパイルした結果
add5.s
(余計なものの削除後)を用いて,
x86-64アセンブリ言語の概要と記号を説明します.
$ gcc -S add5.c
を実行(コンパイル)すると,アセンブリコードadd5.s
が生成されます.(結果は環境依存なので,add5.s
の中身が違ってても気にしなくてOK)- ドット記号
.
で始まるのはアセンブラ命令(assembler directive)です. - コロン記号
:
で終わるのはラベル定義です. - シャープ記号
#
から行末までと,/*
と*/
で囲まれた範囲(複数行可)はコメントです. movq %rsp, %rbp
は機械語命令(2進数)の記号表現(ニモニック(mnemonic))です.movq
が命令でオペコード(opcode),%rsp
と%rbp
は引数でオペランド(operand)と呼ばれます.- ドル記号
$
で始まるのは即値(immediate value,定数)です. - パーセント記号
%
で始まるのはレジスタです. -4(%rbp)
は間接メモリ参照です.この場合は「%rbp-4
の計算結果」をアドレスとするメモリの内容にアクセスすることを意味します.
命令サフィックス
AT&T形式の サイズ指定 | Intel形式の サイズ指定 | メモリオペランドの サイズ | AT&T形式での例 | Intel形式での例 |
---|---|---|---|---|
b | BYTE PTR | 1バイト(8ビット) | movb $10, -8(%rbp) | mov BYTE PTR [rbp-8], 10 |
w | WORD PTR | 2バイト(16ビット) | movw $10, -8(%rbp) | mov WORD PTR [rbp-8], 10 |
l | DWORD PTR | 4バイト(32ビット) | movl $10, -8(%rbp) | mov DWORD PTR [rbp-8], 10 |
q | QWORD PTR | 8バイト(64ビット) | movq $10, -8(%rbp) | mov QWORD PTR [rbp-8], 10 |
- 間接メモリ参照ではサイズ指定が必要です (「何バイト読み書きするのか」が決まらないから)
- AT&T形式では命令サフィックス(instruction suffix)でサイズを指定します.
例えば
movq $10, -8(%rbp)
のq
は, 「メモリ参照-8(%rbp)
への書き込みサイズが8バイト」であることを意味します.
サフィックスとプリフィックス
サフィックス(suffix)は接尾語(後ろに付けるもの), プリフィックス(prefix)は接頭語(前に付けるもの)という意味です.
- Intel形式ではメモリ参照の前に,
BYTE PTR
などと指定します. - 他のオペランドからサイズが決まる場合は命令サフィックスを省略可能です.
例えば,
movq %rax, -8(%rsp)
はmov %rax, -8(%rsp)
にできます.mov
命令の2つのオペランドはサイズが同じで%rax
レジスタのサイズが8バイトだからです.
即値(定数)
種類 | 説明 | 例 |
---|---|---|
10進数定数 | 0 で始まらない | pushq $74 |
16進数定数 | 0x か0X で始まる | pushq $0x4A |
8進数定数 | 0 で始まる | pushq $0112 |
2進数定数 | 0b か0B で始まる | pushq $0b01001010 |
文字定数 | ' (クオート)で始まる | pushq $'J |
' (クオート)で囲む | pushq $'J' | |
\ バックスラッシュでエスケープ可 | pushq $'\n |
- 即値(immediate value,定数)には
$
をつけます - 上の例の定数は最後以外は全部,値が同じです
- GNUアセンブラでは文字定数の値はASCIIコードです.
上の例では,文字
'J'
の値は74
です. - バックスラッシュでエスケープできる文字は,
\b
,\f
,\n
,\r
,\t
,\"
,\\
です. また\123
は8進数,\x4F
は16進数で指定した文字コードになります. - 多くの場合,即値は32ビットまでで, オペランドのサイズが64ビットの場合, 32ビットの即値は,64ビットの演算前に 64ビットに符号拡張 されます (ゼロ拡張だと 負の値が大きな正の値になって困るから)
64ビットに符号拡張される例(1)
# asm/add-imm2.s
.text
.globl main
.type main, @function
main:
movq $0, %rax
addq $-1, %rax
ret
.size main, .-main
上のaddq $-1, %rax
命令の即値$-1
は
32ビット(以下の場合もあります)の符号あり整数$0xFFFFFFFF
として
機械語命令に埋め込まれます.
addq
命令が実行される時は,
この$0xFFFFFFFF
が64ビットに符号拡張されて$0xFFFFFFFFFFFFFFFF
となります.
以下の実行例でもこれが確認できました.
0 + 0xFFFFFFFFFFFFFFFF = 0xFFFFFFFFFFFFFFFF
$ gcc -g add-imm2.s
$ gdb ./a.out -x add-imm2.txt
Breakpoint 1, main () at add-imm2.s:8
8 ret
7 addq $-1, %rax
$1 = 0xffffffffffffffff
# 0xffffffffffffffff が表示されていれば成功
64ビットに符号拡張される例(2)
32ビットの即値が,64ビットの演算前に64ビットに符号拡張されることを見てみます.
32ビット符号あり整数が表現できる範囲は-0x80000000
から0x7FFFFFFF
です.
# asm/add-imm.s
.text
.globl main
.type main, @function
main:
movq $0, %rax
addl $0xFFFFFFFF, %eax # OK (0xFFFFFFFF = -1)
# addq $0xFFFFFFFF, %rax # NG (0x7FFFFFFFを超えている)
addq $0xFFFFFFFFFFFFFFFF, %rax # OK (0xFFFFFFFFFFFFFFFF = -1)
addq $0x7FFFFFFF, %rax # OK
addq $-0x80000000, %rax # OK
addq $0xFFFFFFFF80000000, %rax # OK (0xFFFFFFFF80000000 = -0x80000000)
ret
.size main, .-main
-
7行目の
$0xFFFFFFFF
は32ビット符号あり整数として-1
, つまり32ビット符号あり整数が表現できる範囲内なのでOKです. -
一方,8行目の
$0xFFFFFFFF
は 64ビット符号あり整数として$0x7FFFFFFF
を超えてるのでNGです. (アセンブルするとエラーになります) -
9行目の
$0xFFFFFFFFFFFFFFFF
は一見NGですが, 64ビット符号あり整数としての値は-1
なので, GNUアセンブラはこの即値を-1
として機械語命令に埋め込みます. -
いちばん大事なのは最後の2つの
addq
命令です.addq $-0x80000000, %rax
の 即値$-0x80000000
は(機械語命令中では32ビットで埋め込まれますが) 足し算を実行する前に64ビットに符号拡張されるので,$0xFFFFFFFF80000000
という値で足し算されます. (つまり,$0x80000000
を引きます). 以下の実行例を見ると,その通りの実行結果になっています.❶ 0x17ffffffd - $0x80000000 = ❷ 0xfffffffd
❷ 0xfffffffd - $0x80000000 = ❸ 0x7ffffffd
一方,もし
$-0x80000000
を(符号拡張ではなく) ゼロ拡張 すると,$0x0000000080000000
となるので,$0x80000000
を(引くのではなく)足すことになってしまいます.
$ gcc -g add-imm.s
$ gdb ./a.out -x add-imm.txt
Breakpoint 1, main () at add-imm.s:7
7 addl $0xFFFFFFFF, %eax # OK (0xFFFFFFFF = -0x1)
9 addq $0xFFFFFFFFFFFFFFFF, %rax # OK (0xFFFFFFFFFFFFFFFF = -0x1)
1: /x $rax = 0xffffffff
10 addq $0x7FFFFFFF, %rax # OK
1: /x $rax = 0xfffffffe
11 addq $-0x80000000, %rax # OK
1: /x $rax = ❶ 0x17ffffffd
12 addq $0xFFFFFFFF80000000, %rax # OK (0xFFFFFFFF80000000 = -0x80000000)
1: /x $rax = ❷ 0xfffffffd
main () at add-imm.s:13
13 ret
1: /x $rax = ❸ 0x7ffffffd
#以下が表示されていれば成功
#1: /x $rax = 0xffffffff
#1: /x $rax = 0xfffffffe
#1: /x $rax = 0x17ffffffd
#1: /x $rax = 0xfffffffd
#1: /x $rax = 0x7ffffffd
以下の通り,逆アセンブルすると, 32ビット以下の即値として機械語命令中に埋め込まれていることが分かります.
$ gcc -g add-imm.s
$ objdump -d ./a.out
0000000000001129 <main>:
1129: 48 c7 c0 00 00 00 00 mov $0x0,%rax
1130: 83 c0 ff add $0xffffffff,%eax
1133: 48 83 c0 ff add $0xffffffffffffffff,%rax
1137: 48 05 ff ff ff 7f add $0x7fffffff,%rax
113d: 48 05 00 00 00 80 add $0xffffffff80000000,%rax
1143: 48 05 00 00 00 80 add $0xffffffff80000000,%rax
1149: c3 ret
mov
命令は例外で64ビットの即値を扱えます
64ビットの即値を扱う例
# asm/movqabs-1.s
.text
.globl main
.type main, @function
main:
movq $0x1122334455667788, %rax
movabsq $0x99AABBCCDDEEFF00, %rax
ret
.size main, .-main
$ gcc -g movqabs-1.s
$ gdb ./a.out -x movqabs-1.txt
Breakpoint 1, main () at movqabs-1.s:6
6 movq $0x1122334455667788, %rax
7 movabsq $0x99AABBCCDDEEFF00, %rax
1: /x $rax = 0x1122334455667788
main () at movqabs-1.s:8
8 ret
1: /x $rax = 0x99aabbccddeeff00
# 以下が表示されれば成功
# 1: /x $rax = 0x1122334455667788
# 1: /x $rax = 0x99aabbccddeeff00
- ジャンプでは64ビットの相対ジャンプができないので, 間接ジャンプを使う必要がある
64ビットの相対ジャンプの代わりに間接ジャンプを使う例
# asm/jmp-64bit.s
.text
.globl main
.type main, @function
main:
# jmp 0x1122334455667788 # NG
movq $0x1122334455667788, %rax
jmp *%rax
ret
.size main, .-main
0x1122334455667788
はいい加減なアドレスなので,
コンパイルは可能ですが,実行すると segmentation fault になります.
レジスタ
汎用レジスタ
- 上記16個のレジスタが汎用レジスタ(general-purpose register)です. 原則として,プログラマが自由に使えます.
- ただし,
%rsp
はスタックポインタ,%rbp
はベースポインタと呼び, 一番上のスタックフレームの上下を指す という役割があります. (ただし,-fomit-frame-pointer オプションでコンパイルされたa.out
中では,%rbp
はベースポインタとしてではなく, 汎用レジスタとして使われています).
caller-save/callee-saveレジスタ
汎用レジスタ | |
---|---|
caller-saveレジスタ | %rax , %rcx , %rdx , %rsi , %rdi , %r8 〜%r11 |
callee-saveレジスタ | %rbx , %rbp , %rsp , %r12 〜%r15 |
引数
引数 | レジスタ |
---|---|
第1引数 | %rdi |
第2引数 | %rsi |
第3引数 | %rdx |
第4引数 | %rcx |
第5引数 | %r8 |
第6引数 | %r9 |
- 第7引数以降はレジスタではなくスタックを介して渡します
プログラムカウンタ(命令ポインタ)
ステータスレジスタ(フラグレジスタ)
本書で扱うフラグ
ステータスレジスタのうち,本書は以下の6つのフラグを扱います.
フラグ | 名前 | 説明 |
---|---|---|
CF | キャリーフラグ | 算術演算で結果の最上位ビットにキャリーかボローが生じるとセット.それ以外はクリア.符号なし整数演算でのオーバーフロー状態を表す. |
OF | オーバーフローフラグ | 符号ビット(MSB)を除いて,整数の演算結果が大きすぎるか小さすぎるかするとセット.それ以外はクリア.2の補数表現での符号あり整数演算のオーバーフロー状態を表す. |
ZF | ゼロフラグ | 結果がゼロの時にセット.それ以外はクリア. |
SF | 符号フラグ | 符号あり整数の符号ビット(MSB)と同じ値をセット.(0は正の数,1は負の数であることを表す) |
PF | パリティフラグ | 結果の最下位バイトの値1のビットが偶数個あればセット,奇数個であればクリア. |
AF | 調整フラグ | 算術演算で,結果のビット3にキャリーかボローが生じるとセット.それ以外はクリア.BCD演算で使用する(本書ではほとんど使いません). |
ステータスフラグの変化の記法
x86-64命令を実行すると,ステータスフラグが変化する命令と 変化しない命令があります. ステータスフラグの変化は以下の記法で表します.
記法の意味は以下の通りです.
記法 | 意味 |
---|---|
空白 | フラグ値に変化なし |
! | フラグ値に変化あり |
? | フラグ値は未定義(参照禁止) |
0 | フラグ値はクリア(0になる) |
1 | フラグ値はセット(1になる) |
レジスタの別名
%rax
レジスタの別名 (%rbx
, %rcx
, %rdx
も同様)
%rax
の下位32ビットは%eax
としてアクセス可能%eax
の下位16ビットは%ax
としてアクセス可能%ax
の上位8ビットは%ah
としてアクセス可能%ax
の下位8ビットは%al
としてアクセス可能
%rbp
レジスタの別名 (%rsp
, %rdi
, %rsi
も同様)
%rbp
の下位32ビットは%ebp
としてアクセス可能%ebp
の下位16ビットは%bp
としてアクセス可能%bp
の下位8ビットは%bpl
としてアクセス可能
%r8
レジスタの別名 (%r9
〜%r15
も同様)
%r8
の下位32ビットは%r8d
としてアクセス可能%r8d
の下位16ビットは%r8w
としてアクセス可能%r8w
の下位8ビットは%r8b
としてアクセス可能
同時に使えない制限
- 一部のレジスタは
%ah
,%bh
,%ch
,%dh
と一緒には使えない. - 例:
movb %ah, (%r8)
やmovb %ah, %bpl
はエラーになる. - 正確には
REX
プリフィクス付きの命令では,%ah
,%bh
,%ch
,%dh
を使えない.
32ビットレジスタ上の演算は64ビットレジスタの上位32ビットをゼロにする
- 例:
movl $0xAABBCCDD, %eax
を実行すると%rax
の上位32ビットが全てゼロになる - 例:
movw $0x1122, %ax
やmovb $0x11, %al
では上位をゼロにすることはない
上位32ビットをゼロにする実行例
# asm/zero-upper32.s
.text
.globl main
.type main, @function
main:
movq $0x1122334455667788, %rax
movl $0xAABBCCDD, %eax
movq $0x1122334455667788, %rax
movw $0x1122, %ax
movq $0x1122334455667788, %rax
movb $0x11, %al
ret
.size main, .-main
$ gcc -g zero-upper32.s
$ gdb ./a.out -x zero-upper32.txt
Breakpoint 1, main () at zero-upper32.s:7
7 movl $0xAABBCCDD, %eax
6 movq $0x1122334455667788, %rax
7 movl $0xAABBCCDD, %eax
$1 = 0x1122334455667788
8 movq $0x1122334455667788, %rax
$2 = 0x00000000aabbccdd
# 以下が出力されれば成功
# $1 = 0x1122334455667788 (%raxは8バイトの値を保持)
# $2 = 0x00000000aabbccdd (%raxの上位4バイトがゼロになった)
アドレッシングモード(オペランドの記法)
アドレッシングモードの種類
アドレッシング モードの種類 | オペランドの値 | 例 | 計算するアドレス |
---|---|---|---|
定数の値 | movq $0x100, %rax | ||
movq $foo, %rax | |||
レジスタの値 | movq %rbx, %rax | ||
定数で指定した アドレスのメモリ値 | movq 0x100, %rax | 0x100 | |
movq foo, %rax | foo | ||
レジスタ等で計算した アドレスのメモリ値 | movq (%rsp), %rax | %rsp | |
movq 8(%rsp), %rax | %rsp+8 | ||
movq foo(%rip), %rax | %rip+foo |
foo
はラベルであり,定数と同じ扱い.(定数を書ける場所にはラベルも書ける).- メモリ参照では例えば
-8(%rbp, %rax, 8)
など複雑なオペランドも指定可能. 参照するメモリのアドレスは-8+%rbp+%rax*8
になる. (以下を参照).
メモリ参照の形式
AT&T形式 | Intel形式 | 計算されるアドレス | |
---|---|---|---|
通常のメモリ参照 | disp (base, index, scale) | [base + index * scale + disp] | base + index * scale + disp |
%rip 相対参照 | disp (%rip ) | [rip + disp] | %rip + disp |
注: Intelのマニュアルには「segment: メモリ参照」という形式もあるとありますが, segment: はほとんど使わないので,省いて説明します.
segmentは使わないの?(いえ,ちょっと使います)
%fs
はセグメントレジスタと呼ばれる16ビット長のレジスタで,
他には%cs
,%ds
,%ss
,%es
,%gs
があります.
x86-64では%cs
,%ds
,%ss
,%es
は常にベースアドレスが0と扱われます.
%fs:
という記法が使われた時は,
「%fs
レジスタが示すベースアドレスをアクセスするアドレスに加える」
ことを意味します.
%fs
のベースレジスタの値を得るには次の方法があります.
arch_prctl()
システムコールを使う (ここでは説明しません).gdb
でp/x $fs_base
を実行する. (なお,p/x $fs
を実行すると0
が返りますがこの値は嘘です)rdfsbase
命令を使う.
rdfsbase命令はCPUとOSの設定に依存
rdfsbase
命令が使えるかどうかは,CPUとOSの設定に依存します.
/proc/cpuinfo
のflags
の表示にfsgsbase
があれば,rdfsbase
命令は使えます.
(以下の出力例ではfsgsbase
が入っています).
$ less /proc/cpuinfo
processor : 0
cpu family : 6
model name : Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
(一部略)
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon nopl xtopology tsc_reliable nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch cpuid_fault invpcid_single ssbd ibrs ibpb stibp ibrs_enhanced fsgsbase tsc_adjust bmi1 avx2 smep bmi2 invpcid rdseed adx smap clflushopt xsaveopt xsavec xgetbv1 xsaves arat md_clear flush_l1d arch_capabilities
Linuxでは%fs:
や%gs:
を使って
スレッドローカルストレージ(TLS)を実現しています.
スレッドローカルストレージとは「スレッドごとの(一種の)グローバル変数」です.
マルチスレッドはスレッド同士がメモリ空間を共有しているので,
普通にグローバル変数を使うと,他のスレッドに内容が破壊される可能性があります.
スレッドローカルストレージを使えば,他のスレッドに破壊される心配がなくなります.
スレッドごとに%fs
レジスタの値を変えて,
(プログラム上では同じ変数に見えても)スレッドごとに別のアドレスを
参照させて実現しているのでしょう.
(CPUがスレッドの実行を停止・再開するたびに,
スレッドが使用しているレジスタの値も退避・回復するので,
見た目上,「スレッドはそれぞれ独自のレジスタセットを持っている」ように動作します).
C11からは_Thread_local
,gcc
では__thread
というキーワードで,
スレッドローカルな変数を宣言できます.
// tls.c
#include <stdio.h>
❶ __thread int x = 0xdeadbeef;
int main ()
{
printf ("x=%x\n", x);
}
$ gcc -g tls.c
$ ./a.out
x=deadbeef
$ objdump -d ./a.out | less
0000000000001149 <main>:
1149: f3 0f 1e fa endbr64
114d: 55 push %rbp
114e: 48 89 e5 mov %rsp,%rbp
1151: 64 8b 04 25 fc ff ff mov ❷ %fs:0xfffffffffffffffc,%eax
1158: ff
1159: 89 c6 mov %eax,%esi
$ gdb ./a.out
(gdb) b main
Breakpoint 1 at 0x1151: file tls.c, line 5.
(gdb) r
Breakpoint 1, main () at tls.c:5
5 printf ("x=%x\n", x);
(gdb) p/x x
$1 = 0xdeadbeef
(gdb) ❸ p/x $fs_base
$2 = ❹ 0x7ffff7fa9740
(gdb) x/1wx $fs_base - 4
0x7ffff7fa973c: ❺ 0xdeadbeef
- ❶
__thread
キーワードを使って,変数x
をスレッドローカルにします. - コンパイルした
a.out
を逆アセンブルすると, ❷%fs:0xfffffffffffffffc
というメモリ参照があります. これがスレッドローカルな変数x
の実体の場所で, 「%fsのベースレジスタ - 4
」がx
のアドレスになります. - ❸
p/x $fs_base
を使って,%fsのベースレジスタ
の値を調べると ❹0x7ffff7fa9740
と分かりました. - アドレス
$fs_base - 4
のメモリの中身(4バイト)を調べると, 変数x
の値である❺0xDEADBEEF
が入っていました.
0xDEADBEEFとは
バイナリ上でデバッグする際,「ありそうもない値」を使うと便利なことがあります.
1
や2
だと偶然の一致がありますが,「ありそうもない値」を使うと
「高い確率でこれは私が書き込んだ値だよね」と分かるからです.
0xDEADBEEF
は正しい16進数でありながら,英単語としても読めるので,
「ありそうもない値」として便利です.
他にはCAFEBABE
もよく使われます.
0xDEADBEEF
や0xCAFEBABE
はバイナリを識別するマジックナンバーとしても使われます.
%fs:はスタック保護でも使われる
$ gcc -S -fstack-protector-all add5.c
add5.c
を-fstack-protector-all
オプションで
スタック保護機能をオンにしてコンパイルします.
(最近のLinuxのgcc
のデフォルトでは,-fstack-protector-strong
が有効に
なっています.これはバッファを使用する関数のみにスタック保護機能を加えます.
ここでは-fstack-protector-all
を使って全関数にスタック保護機能を加えました).
.text
.globl add5
.type add5, @function
add5:
endbr64
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp
movl %edi, -20(%rbp)
movq ❶ %fs:40, %rax
movq ❷ %rax, -8(%rbp)
xorl %eax, %eax
movl -20(%rbp), %eax
addl $5, %eax
movq ❸ -8(%rbp), %rdx
subq ❹ %fs:40, %rdx
je .L3
call ❺ __stack_chk_fail@PLT
.L3:
leave
ret
.size add5, .-add5
- 関数の最初の方で,スレッドローカルストレージの❶
%fs:40
の値を, (%rax
経由で)スタック上の-8(%rbp)
に書き込みます. - 関数の終わりの方で,❸
-8(%rbp)
の値を%rdx
レジスタにコピーし, ❹%fs:40
の値を引き算しています. - もし,引き算の結果がゼロでなければ(つまり❸と❹の値が異なれば),
「バッファオーバーフローが起きた」と判断して,
❺
__stack_chk_fail@PLT
を呼び出して プロセスを強制終了させます(つまりバッファオーバーフロー攻撃を防げたことになります).
メモリ参照で可能な組み合わせ(64ビットモードの場合)
通常のメモリ参照
- disp には符号あり定数を指定する.ただし「64ビット定数」は無いことに注意. アドレス計算時に64ビット長に符号拡張される. dispは変位(displacement)を意味する.
- base には上記のいずれかのレジスタを指定可能.省略も可.
- index には上記のいずれかのレジスタを指定可能.省略も可.
%rsp
を指定できないことに注意. - scale を省略すると
1
と同じ
注: dispの例外.
mov␣
命令のみ,64ビットのdispを指定可能. この場合,movabs␣
というニモニックを使用可能. メモリ参照はdispのみで,base,index,scaleは指定不可. 他方のオペランドは%rax
のみ指定可能.movq 0x1122334455667788, %rax movabsq 0x1122334455667788, %rax movq %rax, 0x1122334455667788 movabsq %rax, 0x1122334455667788
%rip
相対参照
%rip
相対参照では32ビットのdispと%rip
レジスタのみが指定可能です.
メモリ参照の例
AT&T形式 | Intel形式 | 指定したもの | 計算するアドレス |
---|---|---|---|
8 | [8] | disp | 8 |
foo | [foo] | disp | foo |
(%rbp) | [rbp] | base | %rbp |
8(%rbp) | [rbp+8] | dispとbase | %rbp + 8 |
foo(%rbp) | [rbp+foo] | dispとbase | %rbp + foo |
8(%rbp,%rax) | [rbp+rax+8] | dispとbaseとindex | %rbp + %rax + 8 |
8(%rbp,%rax, 2) | [rbp+rax*2+8] | dispとbaseとindexとscale | %rbp + %rax*2 + 8 |
(%rip) | [rip] | base | %rip |
8(%rip) | [rip+8] | dispとbase | %rip + 8 |
foo(%rip) | [rip+foo] | dispとbase | %rip + foo |
%fs:-4 | fs:[-4] | segmentとdisp | %fsのベースレジスタ - 4 |
- メモリに読み書きするサイズの指定方法 (先頭アドレスだけだと,何バイト読み書きすればいいのか不明):
-
AT&T形式では命令サフィックス (
q
,l
,w
,b
)で指定する.例:movq $4, 8(%rbp)
-
Intel形式では,メモリ参照の前に
QWORD PTR
,DWORD PTR
,WORD PTR
,BYTE PTR
を付加する (順番に,8バイト,4バイト,2バイト,1バイトを意味する). 例: `mov QWORD PTR [rbp+8], 4
-
「記法」「詳しい記法」欄で用いるオペランドの記法と注意
以下の機械語命令の説明で使う記法を説明します. この記法はその命令に許されるオペランドの形式を表します.
オペランド,即値(定数)
記法 | 例 | 説明 |
---|---|---|
op1 | 第1オペランド | |
op2 | 第2オペランド | |
imm | $100 | imm8, imm16, imm32のどれか |
$foo | ||
imm8 | $100 | 8ビットの即値(定数) |
imm16 | $100 | 16ビットの即値(定数) |
imm32 | $100 | 32ビットの即値(定数) |
- 多くの場合,サイズを省略して単にimmと書きます. 特にサイズに注意が必要な時だけ,imm32などとサイズを明記します.
- 一部例外を除き, x86-64では64ビットの即値を書けません(32ビットまでです).
汎用レジスタ
記法 | 例 | 説明 |
---|---|---|
r | %rax | r8, r16, r32, r64のどれか |
r8 | %al | 8ビットの汎用レジスタ |
r16 | %ax | 16ビットの汎用レジスタ |
r32 | %eax | 32ビットの汎用レジスタ |
r64 | %rax | 64ビットの汎用レジスタ |
メモリ参照
記法 | 例 | 説明 |
---|---|---|
r/m | %rbp | r/m8, r/m16, r/m32, r/m32, r/m64のどれか |
100 | ||
-8(%rbp) | ||
foo(%rbp) | ||
r/m8 | -8(%rbp) | r8 または 8ビットのメモリ参照 |
r/m16 | -8(%rbp) | r16 また は16ビットのメモリ参照 |
r/m32 | -8(%rbp) | r32 また は32ビットのメモリ参照 |
r/m64 | -8(%rbp) | r64 また は64ビットのメモリ参照 |
m | -8(%rbp) | メモリ参照 |
ジャンプ・コール用のオペランド
記法 | 例 | 説明 |
---|---|---|
rel | 0x100 | rel8, rel32のどちらか |
foo | ||
rel8 | 0x100 | 8ビット相対アドレス(直接ジャンプ,定数だが$ は不要) |
rel32 | 0x1000 | 32ビット相対アドレス(直接ジャンプ,定数だが$ は不要) |
*r/m64 | *%rax | r64 または64ビットのメモリ参照による絶対アドレス (間接ジャンプ, * が前に必要) |
*(%rax) | ||
*-8(%rax) |
-
GNUアセンブラの記法のおかしな点
- 直接ジャンプ先の指定relは,定数なのに
$
をつけてはいけない - 間接ジャンプ先の指定**r/m64は,
(他のr/m*オペランドでは不要だったのに)
*
をつけなくてはいけない - 相対アドレスで
rel8
かrel32
をプログラマは選べない (jmp
命令に命令サフィックスb
やl
をつけると怒られる.アセンブラにお任せするしか無い)
- 直接ジャンプ先の指定relは,定数なのに
-
*%rax
と*(%rax)
の違いに注意(以下の図を参照)
データ転送(コピー)系の命令
mov
命令: データの転送(コピー)
記法 | 何の略か | 動作 |
---|---|---|
mov␣ op1, op2 | move | op1の値をop2にデータ転送(コピー) |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
mov␣ r, r/m | movq %rax, %rbx | %rbx = %rax | movq-1.s movq-1.txt |
movq %rax, -8(%rsp) | *(%rsp - 8) = %rax | movq-2.s movq-2.txt | |
mov␣ r/m, r | movq -8(%rsp), %rax | %rax = *(%rsp - 8) | movq-3.s movq-3.txt |
mov␣ imm, r | movq $999, %rax | %rax = 999 | movq-4.s movq-4.txt |
mov␣ imm, r/m | movq $999, -8(%rsp) | *(%rsp - 8) = 999 | movq-5.s movq-5.txt |
␣
は命令サフィックスmov
命令(および他のほとんどのデータ転送命令)はステータスフラグの値を変更しないmov
命令はメモリからメモリへの直接データ転送はできない
xchg
命令: オペランドの値を交換
記法 | 何の略か | 動作 |
---|---|---|
xchg op1, op2 | exchange | op1 と op2 の値を交換する |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
xchg r, r/m | xchg %rax, (%rsp) | %rax と(%rsp) の値を交換する | xchg.s xchg.txt |
xchg r/m, r | xchg (%rsp), %rax | (%rsp) と%rax の値を交換する | xchg.s xchg.txt |
xchg
命令はアトミックに2つのオペランドの値を交換します.(LOCKプリフィクスをつけなくてもアトミックになります)- このアトミックな動作はロックなどの同期機構を作るために使えます.
lea
命令: 実効アドレスを計算
記法 | 何の略か | 動作 |
---|---|---|
lea␣ op1, op2 | load effective address | op1 の実効アドレスを op2 に代入する |
push
とpop
命令: スタックとデータ転送
記法 | 何の略か | 動作 |
---|---|---|
push␣ op1 | push | op1 をスタックにプッシュ |
pop␣ op1 | pop | スタックから op1 にポップ |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
push␣ imm | pushq $999 | %rsp-=8; *(%rsp)=999 | push1.s push1.txt |
push␣ r/m16 | pushw %ax | %rsp-=2; *(%rsp)=%ax | push2.s push2.txt |
push␣ r/m64 | pushq %rax | %rsp-=8; *(%rsp)=%rax | push-pop.s push-pop.txt |
pop␣ r/m16 | popw %ax | *(%rsp)=%ax; %rsp += 2 | pop2.s pop2.txt |
pop␣ r/m64 | popq %rbx | %rbx=*(%rsp); %rsp += 8 | push-pop.s push-pop.txt |
四則演算・論理演算の命令
add
, adc
命令: 足し算
記法 | 何の略か | 動作 |
---|---|---|
add␣ op1, op2 | add | op1 を op2 に加える |
adc␣ op1, op2 | add with carry | op1 と CF を op2 に加える |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
add␣ imm, r/m | addq $999, %rax | %rax += 999 | sub-1.s sub-1.txt |
add␣ r, r/m | addq %rax, (%rsp) | *(%rsp) += %rax | add-2.s add-2.txt |
add␣ r/m, r | addq (%rsp), %rax | %rax += *(%rsp) | add-2.s add-2.txt |
adc␣ imm, r/m | adcq $999, %rax | %rax += 999 + CF | adc-1.s adc-1.txt |
adc␣ r, r/m | adcq %rax, (%rsp) | *(%rsp) += %rax + CF | adc-2.s adc-2.txt |
adc␣ r/m, r | adcq (%rsp), %rax | %rax += *(%rsp) + CF | adc-3.s adc-3.txt |
add
とadc
はオペランドが符号あり整数か符号なし整数かを区別せず, 両方の結果を正しく計算する.
sub
, sbb
命令: 引き算
記法 | 何の略か | 動作 |
---|---|---|
sub␣ op1, op2 | subtract | op1 を op2 から引く |
sbb␣ op1, op2 | subtract with borrow | op1 と CF を op2 から引く |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
sub␣ imm, r/m | subq $999, %rax | %rax -= 999 | sub-1.s sub-1.txt |
sub␣ r, r/m | subq %rax, (%rsp) | *(%rsp) -= %rax | sub-2.s sub-2.txt |
sub␣ r/m, r | subq (%rsp), %rax | %rax -= *(%rsp) | sub-2.s sub-2.txt |
sbb␣ imm, r/m | sbbq $999, %rax | %rax -= 999 + CF | sbb-1.s sbb-1.txt |
sbb␣ r, r/m | sbbq %rax, (%rsp) | *(%rsp) -= %rax + CF | sbb-2.s sbb-2.txt |
sbb␣ r/m, r | sbbq (%rsp), %rax | %rax -= *(%rsp) + CF | sbb-2.s sbb-2.txt |
add
と同様に,sub
とsbb
は オペランドが符号あり整数か符号なし整数かを区別せず, 両方の結果を正しく計算する.
mul
, imul
命令: かけ算
記法 | 何の略か | 動作 |
---|---|---|
mul␣ op1 | unsigned multiply | 符号なし乗算.(%rdx:%rax) = %rax * op1 |
imul␣ op1 | signed multiply | 符号あり乗算.(%rdx:%rax) = %rax * op1 |
imul␣ op1, op2 | signed multiply | 符号あり乗算.op2 *= op1 |
imul␣ op1, op2, op3 | signed multiply | 符号あり乗算.op3 = op1 * op2 |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
mul␣ r/m | mulq %rbx | (%rdx:%rax) = %rax * %rbx | mul-1.s mul-1.txt |
imul␣ r/m | imulq %rbx | (%rdx:%rax) = %rax * %rbx | imul-1.s imul-1.txt |
imul␣ imm, r | imulq $4, %rax | %rax *= 4 | imul-2.s imul-2.txt |
imul␣ r/m, r | imulq %rbx, %rax | %rax *= %rbx | imul-2.s imul-2.txt |
imul␣ imm, r/m, r | imulq $4, %rbx, %rax | %rax = %rbx * 4 | imul-2.s imul-2.txt |
- オペランドが1つの形式では,
%rax
が隠しオペランドになる. このため,乗算の前に%rax
に値をセットしておく必要がある. また,8バイト同士の乗算結果は最大で16バイトになるので, 乗算結果を%rdx
と%rax
に分割して格納する (16バイトの乗算結果の上位8バイトを%rdx
に,下位8バイトを%rax
に格納する). これをここでは(%rdx:%rax)
という記法で表現している. imul
だけ例外的に,オペランドが2つの形式と3つの形式がある. 2つか3つの形式では乗算結果が64ビットを超えた場合, 越えた分は破棄される(乗算結果は8バイトのみ).
div
, idiv
命令: 割り算,余り
記法 | 何の略か | 動作 |
---|---|---|
div␣ op1 | unsigned divide | 符号なし除算と余り%rax = (%rdx:%rax) / op1 %rdx = (%rdx:%rax) % op1 |
idiv␣ op1 | signed divide | 符号あり除算と余り%rax = (%rdx:%rax) / op1 %rdx = (%rdx:%rax) % op1 |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
div␣ r/m | divq %rbx | %rax = (%rdx:%rax) / %rbx %rdx = (%rdx:%rax) % %rbx | div-1.s div-1.txt |
idiv␣ r/m | idivq %rbx | %rax = (%rdx:%rax) / %rbx %rdx = (%rdx:%rax) % %rbx | idiv-1.s idiv-1.txt |
- 16バイトの値
%rdx:%rax
を第1オペランドで割った商が%rax
に入り, 余りが%rdx
に入る. - 隠しオペランドとして
%rdx
と%rax
が使われるので,事前に値を設定しておく必要がある.idiv
を使う場合,もし%rdx
を使わないのであれば,cqto
命令で%rax
を%rdx:%rax
に符号拡張しておくと良い.
inc
, dec
命令: インクリメント,デクリメント
記法 | 何の略か | 動作 |
---|---|---|
inc␣ op1 | increment | op1の値を1つ増加 |
dec␣ op1 | decrement | op1の値を1つ減少 |
inc
やdec
はオーバーフローしてもCFが変化しないところがポイント.
neg
命令: 符号反転
記法 | 何の略か | 動作 |
---|---|---|
neg␣ op1 | negation | 2の補数によるop1の符号反転 |
not
命令: ビット論理演算 (1)
記法 | 何の略か | 動作 |
---|---|---|
not␣ op1 | bitwise not | op1の各ビットの反転 (NOT) |
and
, or
, xor
命令: ビット論理演算 (2)
記法 | 何の略か | 動作 |
---|---|---|
and␣ op1, op2 | bitwise and | op1とop2の各ビットごとの論理積(AND) |
or␣ op1, op2 | bitwise or | op1とop2の各ビットごとの論理和(OR) |
xor␣ op1, op2 | bitwise xor | op1とop2の各ビットごとの排他的論理和(XOR) |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
and␣ imm, r/m | andq $0x0FFF, %rax | %rax &= 0x0FFF | and-1.s and-1.txt |
and␣ r, r/m | andq %rax, (%rsp) | *(%rsp) &= %rax | and-1.s and-1.txt |
and␣ r/m, r | andq (%rsp), %rax | %rax &= *(%rsp) | and-1.s and-1.txt |
or␣ imm, r/m | orq $0x0FFF, %rax | %rax |= 0x0FFF | or-1.s or-1.txt |
or␣ r, r/m | orq %rax, (%rsp) | *(%rsp) |= %rax | or-1.s or-1.txt |
or␣ r/m, r | orq (%rsp), %rax | %rax |= *(%rsp) | or-1.s or-1.txt |
xor␣ imm, r/m | xorq $0x0FFF, %rax | %rax ^= 0x0FFF | xor-1.s xor-1.txt |
xor␣ r, r/m | xorq %rax, (%rsp) | *(%rsp) ^= %rax | xor-1.s xor-1.txt |
xor␣ r/m, r | xorq (%rsp), %rax | %rax ^= *(%rsp) | xor-1.s xor-1.txt |
&
,|
,^
はC言語で,それぞれ,ビットごとの論理積,論理和,排他的論理積です (忘れた人はC言語を復習しましょう).
sal
, sar
, shl
, shr
命令: シフト
記法 | 何の略か | 動作 |
---|---|---|
sal␣ op1[, op2] | shift arithmetic left | 算術左シフト |
shl␣ op1[, op2] | shift logical left | 論理左シフト |
sar␣ op1[, op2] | shift arithmetic right | 算術右シフト |
shr␣ op1[, op2] | shift logical right | 論理右シフト |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
sal␣ r/m | salq %rax | %rax を1ビット算術左シフト | sal-1.s sal-1.txt |
sal␣ imm8, r/m | salq $2, %rax | %rax を2ビット算術左シフト | sal-1.s sal-1.txt |
sal␣ %cl , r/m | salq %cl, %rax | %rax を%cl ビット算術左シフト | sal-1.s sal-1.txt |
shl␣ r/m | shlq %rax | %rax を1ビット論理左シフト | shl-1.s shl-1.txt |
shl␣ imm8, r/m | shlq $2, %rax | %rax を2ビット論理左シフト | shl-1.s shl-1.txt |
shl␣ %cl , r/m | shlq %cl, %rax | %rax を%cl ビット論理左シフト | shl-1.s shl-1.txt |
sar␣ r/m | sarq %rax | %rax を1ビット算術右シフト | sar-1.s sar-1.txt |
sar␣ imm8, r/m | sarq $2, %rax | %rax を2ビット算術右シフト | sar-1.s sar-1.txt |
sar␣ %cl , r/m | sarq %cl, %rax | %rax を%cl ビット算術右シフト | sar-1.s sar-1.txt |
shr␣ r/m | shrq %rax | %rax を1ビット論理右シフト | shr-1.s shr-1.txt |
shr␣ imm8, r/m | shrq $2, %rax | %rax を2ビット論理右シフト | shr-1.s shr-1.txt |
shr␣ %cl , r/m | shrq %cl, %rax | %rax を%cl ビット論理右シフト | shr-1.s shr-1.txt |
- op1[, op2] という記法は「op2は指定してもしなくても良い」という意味です.
- シフトとは(指定したビット数だけ)右か左にビット列をずらすことを意味します. op2がなければ「1ビットシフト」を意味します.
- 論理シフトとは「空いた場所に0を入れる」, 算術シフトとは「空いた場所に符号ビットを入れる」ことを意味します.
- 左シフトの場合は(符号ビットを入れても意味がないので),論理シフトでも算術シフトでも,0を入れます.その結果,算術左シフト
sal
と論理左シフトshl
は全く同じ動作になります. - C言語の符号あり整数に対する右シフト(>>)は算術シフトか論理シフトかは 決まっていません(実装依存です). C言語で,ビット演算は符号なし整数に対してのみ行うようにしましょう.
rol
, ror
, rcl
, rcr
命令: ローテート
記法 | 何の略か | 動作 |
---|---|---|
rol␣ op1[, op2] | rotate left | 左ローテート |
rcl␣ op1[, op2] | rotate left through carry | CFを含めて左ローテート |
ror␣ op1[, op2] | rotate right | 右ローテート |
rcr␣ op1[, op2] | rotate right through carry | CFを含めて右ローテート |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
rol␣ r/m | rolq %rax | %rax を1ビット左ローテート | rol-1.s rol-1.txt |
rol␣ imm8, r/m | rolq $2, %rax | %rax を2ビット左ローテート | rol-1.s rol-1.txt |
rol␣ %cl , r/m | rolq %cl, %rax | %rax を%cl ビット左ローテート | rol-1.s rol-1.txt |
rcl␣ r/m | rclq %rax | %rax を1ビットCFを含めて左ローテート | rcl-1.s rcl-1.txt |
rcl␣ imm8, r/m | rclq $2, %rax | %rax を2ビットCFを含めて左ローテート | rcl-1.s rcl-1.txt |
rcl␣ %cl , r/m | rclq %cl, %rax | %rax を%cl ビットCFを含めて左ローテート | rcl-1.s rcl-1.txt |
ror␣ r/m | rorq %rax | %rax を1ビット右ローテート | ror-1.s ror-1.txt |
ror␣ imm8, r/m | rorq $2, %rax | %rax を2ビット右ローテート | ror-1.s ror-1.txt |
ror␣ %cl , r/m | rorq %cl, %rax | %rax を%cl ビット右ローテート | ror-1.s ror-1.txt |
rcr␣ r/m | rcrq %rax | %rax を1ビットCFを含めて右ローテート | rcr-1.s rcr-1.txt |
rcr␣ imm8, r/m | rcrq $2, %rax | %rax を2ビットCFを含めて右ローテート | rcr-1.s rcr-1.txt |
rcr␣ %cl , r/m | rcrq %cl, %rax | %rax を%cl ビットCFを含めて右ローテート | rcr-1.s rcr-1.txt |
- op1[, op2] という記法は「op2は指定してもしなくても良い」という意味です.
- ローテートは,シフトではみ出したビットを空いた場所に入れます.
- ローテートする方向(右か左),CFを含めるか否かで,4パターンの命令が存在します.
cmp
, test
命令: 比較
cmp
命令
記法 | 何の略か | 動作 |
---|---|---|
cmp␣ op1[, op2] | compare | op1とop2の比較結果をフラグに格納(比較はsub 命令を使用) |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
cmp␣ imm, r/m | cmpq $999, %rax | subq $999, %rax のフラグ変化のみ計算.オペランドは変更なし | cmp-1.s cmp-1.txt |
cmp␣ r, r/m | cmpq %rax, (%rsp) | subq %rax, (%rsp) のフラグ変化のみ計算.オペランドは変更なし | cmp-1.s cmp-1.txt |
cmp␣ r/m, r | cmpq (%rsp), %rax | subq (%rsp), %rax のフラグ変化のみ計算.オペランドは変更なし | cmp-1.s cmp-1.txt |
cmp
命令はフラグ計算だけを行います. (レジスタやメモリは変化しません).cmp
命令は条件付きジャンプ命令と一緒に使うことが多いです. 例えば以下の2命令で「%rax
が(符号あり整数として)1より大きければジャンプする」という意味になります.
cmpq $1, %rax
jg L2
test
命令
記法 | 何の略か | 動作 |
---|---|---|
test␣ op1[, op2] | logical compare | op1とop2の比較結果をフラグに格納(比較はand 命令を使用) |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
test␣ imm, r/m | testq $999, %rax | andq $999, %rax のフラグ変化のみ計算.オペランドは変更なし | test-1.s test-1.txt |
test␣ r, r/m | testq %rax, (%rsp) | andq %rax, (%rsp) のフラグ変化のみ計算.オペランドは変更なし | test-1.s test-1.txt |
test␣ r/m, r | testq (%rsp), %rax | andq (%rsp), %rax のフラグ変化のみ計算.オペランドは変更なし | test-1.s test-1.txt |
cmp
命令と同様に,test
命令はフラグ計算だけを行います. (レジスタやメモリは変化しません).cmp
命令と同様に,test
命令は条件付きジャンプ命令と一緒に使うことが多いです. 例えば以下の2命令で「%rax
が0ならジャンプする」という意味になります.
testq %rax, %rax
jz L2
- 例えば
%rax
が0かどうかを知りたい場合,cmpq $0, %rax
とtestq %rax, %rax
のどちらでも調べることができます. どちらの場合も,ZF==1なら,%rax
が0と分かります (testq %rax, %rax
はビットごとのANDのフラグ変化を計算するので,%rax
がゼロの時だけ,ZF==1となります). コンパイラはtestq %rax, %rax
を使うことが多いです.testq %rax, %rax
の方が命令長が短くなるからです.
movs
, movz
, cbtw
, cqto
命令: 符号拡張とゼロ拡張
movs
, movz
命令
記法(AT&T形式) | 記法(Intel形式) | 何の略か | 動作 |
---|---|---|---|
movs␣␣ op1, op2 | movsx op2, op1 movsxd op2, op1 | move with sign-extention | op1を符号拡張した値をop2に格納 |
movz␣␣ op1, op2 | movzx op2, op1 | move with zero-extention | op1をゼロ拡張した値をop2に格納 |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
movs␣␣ r/m, r | movslq %eax, %rbx | %rbx = %eax を8バイトに符号拡張した値 | movs-movz.s movs-movz.txt |
movz␣␣ r/m, r | movzwq %ax, %rbx | %rbx = %ax を8バイトにゼロ拡張した値 | movs-movz.s movs-movz.txt |
␣␣ に入るもの | 何の略か | 意味 |
---|---|---|
bw | byte to word | 1バイト→2バイトの拡張 |
bl | byte to long | 1バイト→4バイトの拡張 |
bq | byte to quad | 1バイト→8バイトの拡張 |
wl | word to long | 2バイト→4バイトの拡張 |
wq | word to quad | 2バイト→8バイトの拡張 |
lq | long to quad | 4バイト→8バイトの拡張 |
movs
,movz
命令はAT&T形式とIntel形式でニモニックが異なるので注意です.- GNUアセンブラではAT&T形式でも実は
movsx
,movzx
のニモニックが使用できます. ただし逆アセンブルすると,movslq
,movzwq
などのニモニックが表示されるので,movslq
,movzwq
などを使う方が良いでしょう. movzlq
(Intel形式ではmovzxd
)はありません.例えば,%eax
に値を入れると,%rax
の上位32ビットはクリアされるので,movzlq
は不要だからです.- Intel形式では,4バイト→8バイトの拡張の時だけ,
(
movsx
ではなく)movsxd
を使います.
cbtw
, cqto
命令
記法(AT&T形式) | 記法(Intel形式) | 何の略か | 動作 |
---|---|---|---|
**c␣t␣ | c␣␣␣ | convert ␣ to ␣ | %rax (または%eax , %ax , %al )を符号拡張 |
詳しい記法 (AT&T形式) | 詳しい記法 (Intel形式) | 例 | 例の動作 | サンプルコード |
---|---|---|---|---|
cbtw | cbw | cbtw | %al (byte)を%ax (word)に符号拡張 | cbtw.s cbtw.txt |
cwtl | cwde | cwtl | %ax (word)を%eax (long)に符号拡張 | cbtw.s cbtw.txt |
cwtd | cwd | cwtd | %ax (word)を%dx:%ax (double word)に符号拡張 | cbtw.s cbtw.txt |
cltd | cdq | cltd | %eax (long)を%edx:%eax (doube long, quad)に符号拡張 | cbtw.s cbtw.txt |
cltq | cdqe | cltd | %eax (long)を%rax (quad)に符号拡張 | cbtw.s cbtw.txt |
cqto | cqo | cqto | %rax (quad)を%rdx:%rax (octuple)に符号拡張 | cbtw.s cbtw.txt |
cqto
などはidiv
で割り算する前に使うと便利(%rdx:%rax
がidiv
の隠しオペランドなので).- GNUアセンブラはIntel形式のニモニックも受け付ける.
ジャンプ命令
jmp
: 無条件ジャンプ
記法 | 何の略か | 動作 |
---|---|---|
jmp op1 | jump | op1にジャンプ |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
jmp rel | jmp 0x1000 | 0x1000 番地に相対・直接ジャンプ (%rip += 0x1000 ) | jmp.s jmp.txt |
jmp foo | foo 番地に相対・直接ジャンプ (%rip += foo ) | jmp.s jmp.txt | |
jmp r/m | jmp *%rax | *%rax 番地に絶対・間接ジャンプ (%rip = *%rax) ) | jmp.s jmp.txt |
jmp r/m | jmp *(%rax) | *(%rax) 番地に絶対・間接ジャンプ (%rip = *(%rax) ) | jmp.s jmp.txt |
- x86-64では,相対・直接と絶対・間接の組み合わせしかありません. (つまり,相対・間接ジャンプや絶対・直接ジャンプはありません. なお,ここで紹介していないfarジャンプでは絶対・直接もあります).
- 相対・直接ジャンプでは符号ありの8ビット(rel8)か 32ビット(rel32)の整数定数で相対アドレスを指定します. (64ビットの相対アドレスは指定できません.64ビットのジャンプをしたい時は 絶対・間接ジャンプ命令を使います).
- rel8かrel32かはアセンブラが勝手に選んでくれます.
逆に
jmpb
やjmpl
などとサフィックスをつけて指定することはできません. - なぜか,定数なのにrel8やrel32にはドルマーク
$
をつけません. 逆にr/mの前にはアスタリスク*
が必要です. GNUアセンブラのこの部分は一貫性がないので要注意です.
条件付きジャンプの概要
- 条件付きジャンプ命令
j␣
は ステータスフラグ (CF, OF, PF, SF, ZF)をチェックして, 条件が成り立てばジャンプします. - 条件付きジャンプは比較命令と一緒に使うことが多いです.
例えば以下の2命令で「
%rax
が(符号あり整数として)1より大きければジャンプする」という意味になります.
cmpq $1, %rax
jg L2
- 条件付きジャンプ命令のニモニックでは次の用語を使い分けます
- 符号あり整数の大小には less/greater を使う
- 符号なし整数の大小には above/below を使う
条件付きジャンプ: 符号あり整数用
記法 | 何の略か | 動作 | ジャンプ条件 |
---|---|---|---|
jg reljnle rel | jump if greater jump if not less nor equal | op2>op1ならrelにジャンプ !(op2<=op1)ならrelにジャンプ | ZF==0&&SF==OF |
jge reljnl rel | jump if greater or equal jump if not less | op2>=op1ならrelにジャンプ !(op2<op1)ならrelにジャンプ | SF==OF |
jle reljng rel | jump if less or equal jump if not greater | op2<=op1ならrelにジャンプ !(op2>op1)ならrelにジャンプ | ZF==1||SF!=OF |
jl reljnge rel | jump if less jump if not greater nor equal | op2<op1ならrelにジャンプ !(op2>=op1)ならrelにジャンプ | SF!=OF |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
jg rel | cmpq $0, %rax jg foo | if (%rax >0) goto foo | jg.s jg.txt |
jnle rel | cmpq $0, %rax jnle foo | if (!(%rax <=0)) goto foo | jg.s jg.txt |
jge rel | cmpq $0, %rax jge foo | if (%rax >=0) goto foo | jge.s jge.txt |
jnl rel | cmpq $0, %rax jnl foo | if (!(%rax <0)) goto foo | jge.s jge.txt |
jle rel | cmpq $0, %rax jle foo | if (%rax <=0) goto foo | jle.s jle.txt |
jng rel | cmpq $0, %rax jng foo | if (!(%rax >0)) goto foo | jle.s jle.txt |
jl rel | cmpq $0, %rax jl foo | if (%rax <0) goto foo | jl.s jl.txt |
jnge rel | cmpq $0, %rax jnge foo | if (!(%rax >=0)) goto foo | jl.s jl.txt |
- op1 と op2 は条件付きジャンプ命令の直前で使用した
cmp
命令のオペランドを表します. jg
とjnle
は異なるニモニックですが動作は同じです. その証拠にジャンプ条件はZF==0&&SF==OF
と共通です. 他の3つのペア,jge
とjnl
,jle
とjng
,jl
とjnge
も同様です.
条件付きジャンプ: 符号なし整数用
記法 | 何の略か | 動作 | ジャンプ条件 |
---|---|---|---|
ja reljnbe rel | jump if above jump if not below nor equal | op2>op1ならrelにジャンプ !(op2<=op1)ならrelにジャンプ | CF==0&ZF==0 |
jae reljnb rel | jump if above or equal jump if not below | op2>=op1ならrelにジャンプ !(op2<op1)ならrelにジャンプ | CF==0 |
jbe reljna rel | jump if below or equal jump if not above | op2<=op1ならrelにジャンプ !(op2>op1)ならrelにジャンプ | CF==1&&ZF==1 |
jb reljnae rel | jump if below jump if not above nor equal | op2<op1ならrelにジャンプ !(op2>=op1)ならrelにジャンプ | CF==1 |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
ja rel | cmpq $0, %rax ja foo | if (%rax >0) goto foo | ja.s ja.txt |
jnbe rel | cmpq $0, %rax jnbe foo | if (!(%rax <=0)) goto foo | ja.s ja.txt |
jae rel | cmpq $0, %rax jae foo | if (%rax >=0) goto foo | jae.s jae.txt |
jnb rel | cmpq $0, %rax jnb foo | if (!(%rax <0)) goto foo | jae.s jae.txt |
jbe rel | cmpq $0, %rax jbe foo | if (%rax <=0) goto foo | jbe.s jbe.txt |
jna rel | cmpq $0, %rax jna foo | if (!(%rax >0)) goto foo | jbe.s jbe.txt |
jb rel | cmpq $0, %rax jb foo | if (%rax <0) goto foo | jb.s jb.txt |
jnae rel | cmpq $0, %rax jnae foo | if (!(%rax >=0)) goto foo | jb.s jb.txt |
- op1 と op2 は条件付きジャンプ命令の直前で使用した
cmp
命令のオペランドを表します. ja
とjnbe
は異なるニモニックですが動作は同じです. その証拠にジャンプ条件はCF==0&&ZF==0
と共通です. 他の3つのペア,jae
とjnb
,jbe
とjna
,jb
とjnae
も同様です.
条件付きジャンプ: フラグ用
記法 | 何の略か | 動作 | ジャンプ条件 |
---|---|---|---|
jc rel | jump if carry | CF==1 ならrelにジャンプ | CF==1 |
jnc rel | jump if not carry | CF==0 ならrelにジャンプ | CF==0 |
jo rel | jump if overflow | OF==1 ならrelにジャンプ | OF==1 |
jno rel | jump if not overflow | OF==0 ならrelにジャンプ | OF==0 |
js rel | jump if sign | SF==1 ならrelにジャンプ | SF==1 |
jns rel | jump if not sign | SF==0 ならrelにジャンプ | SF==0 |
jz rel je rel | jump if zero jump if equal | ZF==1 ならrelにジャンプ op2==op1ならrelにジャンプ | ZF==1 |
jnz rel jne rel | jump if not zero jump if not equal | ZF==0 ならrelにジャンプ op2!=op1ならrelにジャンプ | ZF==0 |
jp rel jpe rel | jump if parity jump if parity even | PF==1 ならrelにジャンプ | PF==1 |
jnp rel jpo rel | jump if not parity jump if parity odd | PF==0 ならrelにジャンプ | PF==0 |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
jc rel | jc foo | if (CF==1 ) goto foo | jc.s jc.txt |
jnc rel | jnc foo | if (CF==0 ) goto foo | jc.s jc.txt |
jo rel | jo foo | if (OF==1 ) goto foo | jo.s jo.txt |
jno rel | jno foo | if (OF==0 ) goto foo | jo.s jo.txt |
js rel | js foo | if (SF==1 ) goto foo | js.s js.txt |
jns rel | jns foo | if (SF==0 ) goto foo | js.s js.txt |
jz rel | jz foo | if (ZF==1 ) goto foo | jz.s jz.txt |
je rel | cmpq $0, %rax je foo | if (%rax==0 ) goto foo | jz.s jz.txt |
jnz rel | jnz foo | if (ZF==0 ) goto foo | jz.s jz.txt |
jne rel | cmpq $0, %rax jne foo | if (%rax!=0 ) goto foo | jz.s jz.txt |
jp rel | jp foo | if (PF==1 ) goto foo | jp.s jp.txt |
jpe rel | jpe foo | if (PF==1 ) goto foo | jp.s jp.txt |
jnp rel | jnp foo | if (PF==0 ) goto foo | jp.s jp.txt |
jpo rel | jpo foo | if (PF==0 ) goto foo | jp.s jp.txt |
- op1 と op2 は条件付きジャンプ命令の直前で使用した
cmp
命令のオペランドを表します. jz
とje
は異なるニモニックですが動作は同じです. その証拠にジャンプ条件はZF==1
と共通です. 他の3つのペア,jnz
とjne
,jp
とjpe
,jnp
とjpo
も同様です.- AFフラグのための条件付きジャンプ命令は存在しません.
関数呼び出し(コール命令)
call
, ret
命令: 関数を呼び出す,リターンする
記法 | 何の略か | 動作 |
---|---|---|
call op1 | call procedure | %rip をスタックにプッシュしてから op1にジャンプする( pushq %rip; %rip = op1) |
ret | return from procedure | スタックからポップしたアドレスにジャンプする ( popq %rip ) |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
call rel | call foo | 相対・直接の関数コール | call.s call.txt |
call r/m | call *%rax | 絶対・間接の関数コール | call.s call.txt |
ret | ret | 関数からリターン | call.s call.txt |
enter
, leave
命令: スタックフレームを作成する,破棄する
記法 | 何の略か | 動作 |
---|---|---|
enter op1, op2 | make stack frame | サイズop1のスタックフレームを作成する |
leave | discard stack frame | 今のスタックフレームを破棄する |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
enter imm16, imm8 | enter $0x20, $0 | pushq %rbp movq %rsp, %rbp subq $0x20, %rsp | enter.s enter.txt |
leave | leave | movq %rbp, %rsp popq %rbp | enter.s enter.txt |
enter
命令のop2には関数のネストレベルを指定するのですが, C言語では入れ子の関数がない(つまりネストレベルは常にゼロ)なので 常にゼロを指定します.- ただし,
enter
は遅いので通常は使いません. 代わりに同等の動作をするpushq %rbp; movq %rsp, %rbp; subq $
n, %rsp
を使います.
その他
nop
命令
記法 | 何の略か | 動作 |
---|---|---|
nop | no operation | 何もしない(プログラムカウンタのみ増加) |
nop op1 | no operation | 何もしない(プログラムカウンタのみ増加) |
nop
は何もしない命令です(ただしプログラムカウンタ%rip
は増加します).- 機械語命令列の間を(何もせずに)埋めるために使います.
nop
の機械語命令は1バイト長です.nop
r/m という形式の命令は2〜9バイト長のnop
命令になります. 1バイト長のnop
を9個並べるより, 9バイト長のnop
を1個並べた方が,実行が早くなります.- 「複数バイトの
nop
命令がある」という知識は, 逆アセンブル時にnopl (%rax)
などを見てビックリしないために必要です.
cmpxchg
, cmpxchg8b
, cmpxchg16b
命令: CAS (compare-and-swap)命令
cmpxchg
命令
記法 | 何の略か | 動作 |
---|---|---|
cmpxchg op1, op2 | compare and exchange | %rax とop2を比較し,同じならop2=op1,異なれば %rax =op2 |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
cmpxchg r, r/m | cmpxchg %rbx, (%rsp) | if (*(%rsp)==%rax ) *(%rsp)=%rbx ;else %rax=*(%rsp) ; | cmpxchg.s cmpxchg.txt |
cmpxchg
命令などのCAS命令は,lock-free,つまりロックを使わず 同期機構を実現するために使われます. アトミックに実行する必要があるため,通常,LOCKプリフィックスをつけて使います.- 気持ち:
- あるメモリにあるop2を新しい値op1で書き換えたい.
- ただし,代入前のop2の値は
%rax
と同じはずで, もし(割り込まれて)知らない間に別の値になっていたら,この代入は失敗させる. - 代入が失敗したことを知るために,
(他の誰かが更新した最新の)op2の値を
%rax
に入れる.cmpxchg
実行後に%rax
の値を調べれば,無事にop1への代入ができたかどうかが分かる.
cmpxchg8b
, cmpxchg16b
命令
記法 | 何の略か | 動作 |
---|---|---|
cmpxchg8b op1 | compare and exchange bytes | %edx:%eax とop1を比較し,同じならop1=%ecx:%ebx ,異なれば %edx:%eax =op1 |
cmpxchg16b op1 | compare and exchange bytes | %rdx:%rax とop1を比較し,同じならop1=%rcx:%rbx ,異なれば %rdx:%rax =op1 |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
cmpxchg8b m64 | cmpxchg8b (%rsp) | if (*(%rsp)==%edx:%eax ) *(%rsp)=%ecx:%ebx ;else %edx:%eax=*(%rsp) ; | cmpxchg8b.s cmpxchg8.txt |
cmpxchg16b m128 | cmpxchg16b (%rsp) | if (*(%rsp)==%rdx:%rax ) *(%rsp)=%rcx:%rbx ;else %rdx:%rax=*(%rsp) ; | cmpxchg16b.s cmpxchg16.txt |
cmpxchg8b
,cmpxchg16b
もCAS命令の一種ですが,cmpxchg
とステータスフラグの変化が異なるので,分けて書いています.cmpxchg16b
命令が参照するメモリは16バイト境界のアラインメントが必要です. (つまりメモリアドレスが16の倍数である必要があります).
rdtsc
, rdtscp
命令: タイムスタンプを読む
記法 | 何の略か | 動作 |
---|---|---|
rdtsc | read time-stamp counter | %edx:%eax = 64ビットタイムスタンプカウンタ |
rdtscp | read time-stamp counter and processor ID | %edx:%eax = 64ビットタイムスタンプカウンタ %ecx = 32ビットプロセッサID |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
rdtsc | rdtsc | %edx:%eax = 64ビットタイムスタンプカウンタ | rdtsc.s rdtsc.txt |
rdtscp | rdtscp | %edx:%eax = 64ビットタイムスタンプカウンタ %ecx = 32ビットプロセッサID | rdtscp.s rdtscp.txt |
- x86-64は64ビットのタイムスタンプカウンタ (TSC: time stamp counter)を備えており, リセット後のCPUのサイクル数を数えています. 原理的には「サイクル数の差分をCPUのクロック周波数で割れば実行時間が得られる」 はずですが,実際にはout-of-order実行や, 内部クロックの変化などを考慮する必要があります. 詳しくはHow to Benchmark Code Execution Times on Intel® IA-32 and IA-64 Instruction Set Architectures を参照して下さい.
rdtscp
命令を使うと,プロセッサIDも取得できます.rdtsc
とrdtscp
ではシリアライズ処理が異なるため,得られるサイクル数も異なります. 詳しくは x86-64のマニュアルSDM を参照して下さい.
int3
命令
記法 | 何の略か | 動作 |
---|---|---|
int3 | call to interrupt procedure | ブレークポイントトラップを発生 |
int3
命令はブレークポイントトラップ(ソフトウェア割り込みの一種)を発生させます. 通常実行ではint3
を実行した時点でプロセスは強制終了となりますが, デバッガ上ではその時点でブレークします.continueコマンドでその後の実行も継続できます.ブレークしたい場所が分かっている場合は, Cコード中にasm ("int3");
と書くことでデバッガ上でブレークさせることができます.
ud2
命令
記法 | 何の略か | 動作 |
---|---|---|
ud2 | undefined instruction | 無効オペコード例外を発生させる |
ud2
命令は無効オペコード例外を発生させます. 通常実行ではud2
を実行した時点でプロセスは, シグナルSIGILL
(illegal instruction)を受け取り,強制終了となります デバッガ上でも,Program received signal SIGILL, Illegal instruction.
というメッセージが出て,プロセスは終了になります. 本書では「実行が通るはずがない場所が本当かどうか」の確認のためud2
を使います.(通るはずがない場所にud2
を置いて,SIGILL
が発生しなければOKです)
例外 (exception)とは
例外(exception)はCPUが発生させる割り込み(ソフトウェア割り込み)です. Intel用語で,例外はさらにフォールト(fault),トラップ(trap), アボート(abort)に分類されます. 例えばゼロ割はフォールト,ブレークポイントはトラップです. マイOS作りたい人は頑張って勉強して下さい.
endbr64
命令
記法 | 何の略か | 動作 |
---|---|---|
endbr64 | end branch 64 bit | 間接ジャンプ先として許す |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
endbr64 | endbr64 | 間接ジャンプ先として許す | endbr64.s endbr64.txt |
- Intel CET IBT技術に対応したCPUの場合,
間接ジャンプ後のジャンプ先が
endbr64
以外だった場合, 例外が発生してプログラムは強制終了となります. - Intel CET IBT技術に未対応のCPUの場合は,
nop
命令として動作します. - 逆アセンブルして
endbr64
を見てもビックリしないためにこの説明を書いています. - 私のPCが古すぎて,Intel CET未対応だったため,2023/8/17現在,クラッシュが発生するサンプルコードを作れていません.
bnd
プリフィクス
Intel MPX (Memory Protection Extensions)の機能の一部で,
制御命令 (ジャンプ命令やリターン命令など)に指定できます.
BND0
からBND3
レジスタに指定した境界に対して境界チェックを行います.
この機能をサポートしてないCPUではnop
として動作します.
- 逆アセンブルして
bnd
を見てもビックリしないためにこの説明を書いています.
以下のようにPLTセクションを見ると❶bnd
が使われています.
$ objdump -d /bin/ls | less
(中略)
Disassembly of section .plt:
0000000000004030 <.plt>:
4030: ff 35 2a dc 01 00 push 0x1dc2a(%rip) # 21c60 <_ob
stack_memory_used@@Base+0x114b0>
4036: f2 ff 25 2b dc 01 00 ❶ bnd jmp *0x1dc2b(%rip) # 21c68 <_obstack_memory_used@@Base+0x114b8>
403d: 0f 1f 00 nopl (%rax)
set␣
命令: ステータスフラグの値を取得
記法 | 何の略か | 動作 |
---|---|---|
set␣ op1 | set byte on condition | if (条件␣が成立) op1=1; else op1=0; |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
set␣ r/m8 | setz %al | %al = ZF | setz.s setz.txt |
setg %al | より大きい(greater)条件が成立なら%al =1,違えば%al =0 | setg.s setg.txt |
set␣
命令はステータスフラグの値を取得します.␣
には条件付きジャンプ命令j␣
の␣
と同じものをすべて入れられます.
ストリング命令
movs
などのストリング命令はREPプリフィクスと組み合わせて使います.
- REPプリフィクス
記法 | 何の略か | 動作 |
---|---|---|
rep insn | repeat | %ecx==0 になるまで命令insnと %ecx-- を繰り返し実行 |
repe insn | repeat while equal | %ecx==0 またはフラグZF==0になるまで命令insnと %ecx-- を繰り返し実行 |
repz insn | repeat while zero | |
repne insn | repeat while not equal | %ecx==0 またはフラグZF==1になるまで命令insnと %ecx-- を繰り返し実行 |
repnz insn | repeat while not zero |
詳しい記法 | 例 | 例の動作 | サンプルコード |
---|---|---|---|
rep insn | rep movsb | while (%ecx-- > 0) (*%rdi++) = (*%rsi++); # 1バイトずつコピー | rep.s rep.txt |
repe insn repz insn | repe cmpsb repz cmpsb | while (%ecx-- > 0 && (*%rdi++ == *%rsi++)); # 1バイトずつ比較 | repe.s repe.txt repz.s repz.txt |
repne insn repnz insn | repne cmpsb repnz cmpsb | while (%ecx-- > 0 && (*%rdi++ != *%rsi++)); # 1バイトずつ比較 | repne.s repne.txt repnz.s repnz.txt |
注意: DFフラグ(direction flag)が0の場合,
%rsi
と%rdi
を増やす.DFが1の場合は減らす.上記の説明はDF==0を仮定.
注意: ストリング命令はセグメントレジスタ
%ds
と%es
を使って,%ds:(%rsi)
と%es:(%rdi)
にアクセスします.が,x86-64では%ds
も%es
もベースレジスタをゼロと扱うので,%ds
と%es
は無視して構いません.
-
ストリング命令
記法 何の略か 動作 movs␣
move string (%rsi)
を(%rdi)
にnバイト転送;%rsi
+= n;%rdi
+= n;lods␣
load string (%rsi)
を%rax
にnバイト転送;%rsi
+= n;stos␣
store string %rax
を(%rdi)
にnバイト転送;%rdi
+= n;ins␣
input string I/Oポート %dx
から(%rdi)
にnバイト転送;%rdi
+= n;outs␣
output string (%rsi)
からI/Oポート%dx
にnバイト転送;%rsi
+= n;cmps␣
compare string (%rsi)
と(%rdi)
をnバイト比較;%rsi
+= n;%rdi
+= n;scas␣
scan string %rax
と(%rdi)
をnバイト比較;%rdi
+= n;詳しい記法 例 例の動作 サンプルコード rep movsb
rep movsb
while (%ecx-- > 0) (*%rdi++) = (*%rsi++);
# 1バイトずつコピーrep.s rep.txt rep lodsb
rep lodsb
while (%ecx-- > 0) %al = (*%rsi++);
# 1バイトずつコピーlods.s lods.txt rep stosq
rep stosq
while (%ecx-- > 0) {(*%rdi) = %rax; %rdi+=8; }
# 8バイトずつコピーstos.s stos.txt repe cmpsb
repe cmpsb
while (%ecx-- > 0 && (*%rdi++) == (*%rsi++);
# 1バイトずつ比較repe.s repe.txt repne scasb
repne scasb
while (%ecx-- > 0 && (*%rdi++) != %rax);
# 1バイトずつ比較scas.s scas.txt ␣
にはb
,w
,l
,q
が入り,それぞれ, メモリ参照のサイズ(上ではnと表記)が1バイト,2バイト,4バイト,8バイトになる. (ただし,ins
とouts
はb
,w
,l
のみ指定可能).%rax
はオペランドサイズにより,%rax
,%eax
,%ax
,%al
のいずれかになる.ins␣
とout␣
の実例はここでは無し.
-
DFフラグ(方向フラグ)と
cld
命令・std
命令記法 何の略か 動作 cld
clear direction flag DF=0 std
set direction flag DF=1 - DFフラグはストリング命令で,
%rsi
と%rdi
を増減する方向を決めます.- DF=0 の時は
%rsi
と%rdi
を増やします - DF=1 の時は
%rsi
と%rdi
を減らします
- DF=0 の時は
- DFフラグの変更は
cld
やstd
で行います (一般的にフラグレジスタの値を変更する場合,pushf
でフラグレジスタの値を保存し,popf
で元に戻すのが安全です). - Linux AMD64のABIにより,
関数の出入り口ではDF=0に戻す必要があります.このお約束のため,
自分で
std
していなければ,必ずDF==0となります(わざわざcld
する必要はありません).
- DFフラグはストリング命令で,