x86-64 命令一覧

概要と記号

add5.cgcc -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形式での例
bBYTE PTR1バイト(8ビット)movb $10, -8(%rbp)mov BYTE PTR [rbp-8], 10
wWORD PTR2バイト(16ビット)movw $10, -8(%rbp)mov WORD PTR [rbp-8], 10
lDWORD PTR4バイト(32ビット)movl $10, -8(%rbp)mov DWORD PTR [rbp-8], 10
qQWORD PTR8バイト(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進数定数0x0Xで始まるpushq $0x4A
8進数定数0で始まるpushq $0112
2進数定数0b0Bで始まる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命令を実行すると,ステータスフラグが変化する命令と 変化しない命令があります. ステータスフラグの変化は以下の記法で表します.

CFOFSFZFPFAF
 !?01

記法の意味は以下の通りです.

記法意味
空白フラグ値に変化なし
!フラグ値に変化あり
?フラグ値は未定義(参照禁止)
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, %axmovb $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, %rax0x100
movq foo, %raxfoo

間接メモリ参照

レジスタ等で計算した
アドレスのメモリ値
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は使わないの?(いえ,ちょっと使います)
segment(正確にはsegment-override)はx86-64ではほとんど使いません. が,segmentを使ったメモリ参照をする場合があります. 例えば,`%fs:0xfffffffffffffffc`がその例です.

%fsセグメントレジスタと呼ばれる16ビット長のレジスタで, 他には%cs%ds%ss%es%gsがあります. x86-64では%cs%ds%ss%esは常にベースアドレスが0と扱われます. %fs:という記法が使われた時は, 「%fsレジスタが示すベースアドレスをアクセスするアドレスに加える」 ことを意味します.

%fsのベースレジスタの値を得るには次の方法があります.

  • arch_prctl()システムコールを使う (ここでは説明しません).
  • gdbp/x $fs_baseを実行する. (なお,p/x $fsを実行すると0が返りますがこの値は嘘です)
  • rdfsbase命令を使う.
rdfsbase命令はCPUとOSの設定に依存

rdfsbase命令が使えるかどうかは,CPUとOSの設定に依存します. /proc/cpuinfoflagsの表示に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_localgccでは__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とは

バイナリ上でデバッグする際,「ありそうもない値」を使うと便利なことがあります. 12だと偶然の一致がありますが,「ありそうもない値」を使うと 「高い確率でこれは私が書き込んだ値だよね」と分かるからです. 0xDEADBEEFは正しい16進数でありながら,英単語としても読めるので, 「ありそうもない値」として便利です. 他にはCAFEBABEもよく使われます. 0xDEADBEEF0xCAFEBABEはバイナリを識別するマジックナンバーとしても使われます.

%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]disp8
foo[foo]dispfoo
(%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:-4fs:[-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$100imm8, imm16, imm32のどれか
$foo
imm8$1008ビットの即値(定数)
imm16$10016ビットの即値(定数)
imm32$10032ビットの即値(定数)
  • 多くの場合,サイズを省略して単にimmと書きます. 特にサイズに注意が必要な時だけ,imm32などとサイズを明記します.
  • 一部例外を除き, x86-64では64ビットの即値を書けません(32ビットまでです).

汎用レジスタ

記法説明
r%raxr8, r16, r32, r64のどれか
r8%al8ビットの汎用レジスタ
r16%ax16ビットの汎用レジスタ
r32%eax32ビットの汎用レジスタ
r64%rax64ビットの汎用レジスタ

メモリ参照

記法説明
r/m%rbpr/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) メモリ参照

ジャンプ・コール用のオペランド

記法説明
rel0x100rel8, rel32のどちらか
foo
rel80x1008ビット相対アドレス(直接ジャンプ,定数だが$は不要)
rel320x100032ビット相対アドレス(直接ジャンプ,定数だが$は不要)
*r/m64*%raxr64 または64ビットのメモリ参照による絶対アドレス
(間接ジャンプ,*が前に必要)
*(%rax)
*-8(%rax)
  • GNUアセンブラの記法のおかしな点

    • 直接ジャンプ先の指定relは,定数なのに$をつけてはいけない
    • 間接ジャンプ先の指定**r/m64は, (他のr/m*オペランドでは不要だったのに) *をつけなくてはいけない
    • 相対アドレスでrel8rel32をプログラマは選べない (jmp命令に命令サフィックスblをつけると怒られる.アセンブラにお任せするしか無い)
  • *%rax*(%rax)の違いに注意(以下の図を参照)

データ転送(コピー)系の命令

mov命令: データの転送(コピー)


記法何の略か動作
mov␣ op1, op2moveop1の値をop2にデータ転送(コピー)

詳しい記法例の動作サンプルコード
mov␣ r, r/mmovq %rax, %rbx%rbx = %raxmovq-1.s movq-1.txt
movq %rax, -8(%rsp)*(%rsp - 8) = %raxmovq-2.s movq-2.txt
mov␣ r/m, rmovq -8(%rsp), %rax%rax = *(%rsp - 8)movq-3.s movq-3.txt
mov␣ imm, rmovq $999, %rax%rax = 999movq-4.s movq-4.txt
mov␣ imm, r/mmovq $999, -8(%rsp)*(%rsp - 8) = 999movq-5.s movq-5.txt


  • 命令サフィックス
  • mov命令(および他のほとんどのデータ転送命令)はステータスフラグの値を変更しない
  • mov命令はメモリからメモリへの直接データ転送はできない

xchg命令: オペランドの値を交換


記法何の略か動作
xchg op1, op2exchangeop1op2 の値を交換する

詳しい記法例の動作サンプルコード
xchg r, r/mxchg %rax, (%rsp)%rax(%rsp)の値を交換するxchg.s xchg.txt
xchg r/m, rxchg (%rsp), %rax(%rsp)%raxの値を交換するxchg.s xchg.txt

  • xchg命令はアトミックに2つのオペランドの値を交換します.(LOCKプリフィクスをつけなくてもアトミックになります)
  • このアトミックな動作はロックなどの同期機構を作るために使えます.

lea命令: 実効アドレスを計算


記法何の略か動作
lea␣ op1, op2load effective addressop1 の実効アドレスを op2 に代入する

詳しい記法例の動作サンプルコード
lea␣ m, rleaq -8(%rsp, %rsi, 4), %rax%rax=%rsp+%rsi*4-8lea.s lea.txt

pushpop命令: スタックとデータ転送


記法何の略か動作
push␣ op1pushop1 をスタックにプッシュ
pop␣ op1popスタックから op1 にポップ

詳しい記法例の動作サンプルコード
push␣ immpushq $999%rsp-=8; *(%rsp)=999push1.s push1.txt
push␣ r/m16pushw %ax%rsp-=2; *(%rsp)=%axpush2.s push2.txt
push␣ r/m64pushq %rax%rsp-=8; *(%rsp)=%raxpush-pop.s push-pop.txt
pop␣ r/m16popw %ax*(%rsp)=%ax; %rsp += 2pop2.s pop2.txt
pop␣ r/m64popq %rbx%rbx=*(%rsp); %rsp += 8push-pop.s push-pop.txt

四則演算・論理演算の命令

add, adc命令: 足し算


記法何の略か動作
add␣ op1, op2addop1op2 に加える
adc␣ op1, op2add with carryop1 と CF を op2 に加える

詳しい記法例の動作サンプルコード
add␣ imm, r/maddq $999, %rax%rax += 999sub-1.s sub-1.txt
add␣ r, r/maddq %rax, (%rsp)*(%rsp) += %raxadd-2.s add-2.txt
add␣ r/m, raddq (%rsp), %rax%rax += *(%rsp)add-2.s add-2.txt
adc␣ imm, r/madcq $999, %rax%rax += 999 + CFadc-1.s adc-1.txt
adc␣ r, r/madcq %rax, (%rsp)*(%rsp) += %rax + CFadc-2.s adc-2.txt
adc␣ r/m, radcq (%rsp), %rax%rax += *(%rsp) + CFadc-3.s adc-3.txt

CFOFSFZFPFAF
!!!!!!
  • addadcはオペランドが符号あり整数か符号なし整数かを区別せず, 両方の結果を正しく計算する.

sub, sbb命令: 引き算


記法何の略か動作
sub␣ op1, op2subtractop1op2 から引く
sbb␣ op1, op2subtract with borrowop1 と CF を op2 から引く

詳しい記法例の動作サンプルコード
sub␣ imm, r/msubq $999, %rax%rax -= 999sub-1.s sub-1.txt
sub␣ r, r/msubq %rax, (%rsp)*(%rsp) -= %raxsub-2.s sub-2.txt
sub␣ r/m, rsubq (%rsp), %rax%rax -= *(%rsp)sub-2.s sub-2.txt
sbb␣ imm, r/msbbq $999, %rax%rax -= 999 + CFsbb-1.s sbb-1.txt
sbb␣ r, r/msbbq %rax, (%rsp)*(%rsp) -= %rax + CFsbb-2.s sbb-2.txt
sbb␣ r/m, rsbbq (%rsp), %rax%rax -= *(%rsp) + CFsbb-2.s sbb-2.txt

CFOFSFZFPFAF
!!!!!!
  • addと同様に,subsbbは オペランドが符号あり整数か符号なし整数かを区別せず, 両方の結果を正しく計算する.

mul, imul命令: かけ算


記法何の略か動作
mul␣ op1unsigned multiply符号なし乗算.(%rdx:%rax) = %rax * op1
imul␣ op1signed multiply符号あり乗算.(%rdx:%rax) = %rax * op1
imul␣ op1, op2signed multiply符号あり乗算.op2 *= op1
imul␣ op1, op2, op3signed multiply符号あり乗算.op3 = op1 * op2

詳しい記法例の動作サンプルコード
mul␣ r/mmulq %rbx(%rdx:%rax) = %rax * %rbxmul-1.s mul-1.txt
imul␣ r/mimulq %rbx(%rdx:%rax) = %rax * %rbximul-1.s imul-1.txt
imul␣ imm, rimulq $4, %rax%rax *= 4imul-2.s imul-2.txt
imul␣ r/m, rimulq %rbx, %rax%rax *= %rbximul-2.s imul-2.txt
imul␣ imm, r/m, rimulq $4, %rbx, %rax%rax = %rbx * 4imul-2.s imul-2.txt

CFOFSFZFPFAF
!!????
  • オペランドが1つの形式では,%raxが隠しオペランドになる. このため,乗算の前に%raxに値をセットしておく必要がある. また,8バイト同士の乗算結果は最大で16バイトになるので, 乗算結果を%rdx%raxに分割して格納する (16バイトの乗算結果の上位8バイトを%rdxに,下位8バイトを%raxに格納する). これをここでは(%rdx:%rax)という記法で表現している.
  • imulだけ例外的に,オペランドが2つの形式と3つの形式がある. 2つか3つの形式では乗算結果が64ビットを超えた場合, 越えた分は破棄される(乗算結果は8バイトのみ).

div, idiv命令: 割り算,余り


記法何の略か動作
div␣ op1unsigned divide符号なし除算と余り
%rax = (%rdx:%rax) / op1
%rdx = (%rdx:%rax) % op1
idiv␣ op1signed divide符号あり除算と余り
%rax = (%rdx:%rax) / op1
%rdx = (%rdx:%rax) % op1

詳しい記法例の動作サンプルコード
div␣ r/mdivq %rbx%rax = (%rdx:%rax) / %rbx
%rdx = (%rdx:%rax) % %rbx
div-1.s div-1.txt
idiv␣ r/midivq %rbx%rax = (%rdx:%rax) / %rbx
%rdx = (%rdx:%rax) % %rbx
idiv-1.s idiv-1.txt

CFOFSFZFPFAF
??????
  • 16バイトの値 %rdx:%rax を第1オペランドで割った商が%raxに入り, 余りが%rdxに入る.
  • 隠しオペランドとして%rdx%raxが使われるので,事前に値を設定しておく必要がある. idivを使う場合,もし%rdxを使わないのであれば, cqto命令で%rax%rdx:%raxに符号拡張しておくと良い.

inc, dec命令: インクリメント,デクリメント


記法何の略か動作
inc␣ op1incrementop1の値を1つ増加
dec␣ op1decrementop1の値を1つ減少

詳しい記法例の動作サンプルコード
inc␣ r/minc %rax%rax++inc-1.s inc-1.txt
dec␣ r/mdec %rax%rax--dec-1.s dec-1.txt

  • incdecはオーバーフローしてもCFが変化しないところがポイント.

neg命令: 符号反転


記法何の略か動作
neg␣ op1negation2の補数によるop1の符号反転

詳しい記法例の動作サンプルコード
neg␣ r/mneg %rax%rax = -%raxneg-1.s neg-1.txt

CFOFSFZFPFAF
!!!!!!

not命令: ビット論理演算 (1)


記法何の略か動作
not␣ op1bitwise notop1の各ビットの反転 (NOT)

詳しい記法例の動作サンプルコード
not␣ r/mnotq %rax%rax = ~%raxnot-1.s not-1.txt

and, or, xor命令: ビット論理演算 (2)


記法何の略か動作
and␣ op1, op2bitwise andop1op2の各ビットごとの論理積(AND)
or␣ op1, op2bitwise orop1op2の各ビットごとの論理和(OR)
xor␣ op1, op2bitwise xorop1op2の各ビットごとの排他的論理和(XOR)

詳しい記法例の動作サンプルコード
and␣ imm, r/mandq $0x0FFF, %rax%rax &= 0x0FFFand-1.s and-1.txt
and␣ r, r/mandq %rax, (%rsp)*(%rsp) &= %raxand-1.s and-1.txt
and␣ r/m, randq (%rsp), %rax%rax &= *(%rsp)and-1.s and-1.txt
or␣ imm, r/morq $0x0FFF, %rax%rax |= 0x0FFF or-1.s or-1.txt
or␣ r, r/morq %rax, (%rsp)*(%rsp) |= %raxor-1.s or-1.txt
or␣ r/m, rorq (%rsp), %rax%rax |= *(%rsp)or-1.s or-1.txt
xor␣ imm, r/mxorq $0x0FFF, %rax%rax ^= 0x0FFFxor-1.s xor-1.txt
xor␣ r, r/mxorq %rax, (%rsp)*(%rsp) ^= %raxxor-1.s xor-1.txt
xor␣ r/m, rxorq (%rsp), %rax%rax ^= *(%rsp)xor-1.s xor-1.txt

CFOFSFZFPFAF
00!!!?

xyx & yx | yx ^ y
00000
01011
10011
11110
  • &, |, ^は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/msalq %rax%raxを1ビット算術左シフトsal-1.s sal-1.txt
sal␣ imm8, r/msalq $2, %rax%raxを2ビット算術左シフトsal-1.s sal-1.txt
sal␣ %cl, r/msalq %cl, %rax%rax%clビット算術左シフトsal-1.s sal-1.txt
shl␣ r/mshlq %rax%raxを1ビット論理左シフトshl-1.s shl-1.txt
shl␣ imm8, r/mshlq $2, %rax%raxを2ビット論理左シフトshl-1.s shl-1.txt
shl␣ %cl, r/mshlq %cl, %rax%rax%clビット論理左シフトshl-1.s shl-1.txt
sar␣ r/msarq %rax%raxを1ビット算術右シフトsar-1.s sar-1.txt
sar␣ imm8, r/msarq $2, %rax%raxを2ビット算術右シフトsar-1.s sar-1.txt
sar␣ %cl, r/msarq %cl, %rax%rax%clビット算術右シフトsar-1.s sar-1.txt
shr␣ r/mshrq %rax%raxを1ビット論理右シフトshr-1.s shr-1.txt
shr␣ imm8, r/mshrq $2, %rax%raxを2ビット論理右シフトshr-1.s shr-1.txt
shr␣ %cl, r/mshrq %cl, %rax%rax%clビット論理右シフトshr-1.s shr-1.txt

CFOFSFZFPFAF
!!!!!?
  • 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 carryCFを含めて左ローテート
ror␣ op1[, op2]rotate right右ローテート
rcr␣ op1[, op2]rotate right through carryCFを含めて右ローテート

詳しい記法例の動作サンプルコード
rol␣ r/mrolq %rax%raxを1ビット左ローテートrol-1.s rol-1.txt
rol␣ imm8, r/mrolq $2, %rax%raxを2ビット左ローテートrol-1.s rol-1.txt
rol␣ %cl, r/mrolq %cl, %rax%rax%clビット左ローテートrol-1.s rol-1.txt
rcl␣ r/mrclq %rax%raxを1ビットCFを含めて左ローテートrcl-1.s rcl-1.txt
rcl␣ imm8, r/mrclq $2, %rax%raxを2ビットCFを含めて左ローテートrcl-1.s rcl-1.txt
rcl␣ %cl, r/mrclq %cl, %rax%rax%clビットCFを含めて左ローテートrcl-1.s rcl-1.txt
ror␣ r/mrorq %rax%raxを1ビット右ローテートror-1.s ror-1.txt
ror␣ imm8, r/mrorq $2, %rax%raxを2ビット右ローテートror-1.s ror-1.txt
ror␣ %cl, r/mrorq %cl, %rax%rax%clビット右ローテートror-1.s ror-1.txt
rcr␣ r/mrcrq %rax%raxを1ビットCFを含めて右ローテートrcr-1.s rcr-1.txt
rcr␣ imm8, r/mrcrq $2, %rax%raxを2ビットCFを含めて右ローテートrcr-1.s rcr-1.txt
rcr␣ %cl, r/mrcrq %cl, %rax%rax%clビットCFを含めて右ローテートrcr-1.s rcr-1.txt

  • op1[, op2] という記法は「op2は指定してもしなくても良い」という意味です.
  • ローテートは,シフトではみ出したビットを空いた場所に入れます.
  • ローテートする方向(右か左),CFを含めるか否かで,4パターンの命令が存在します.

cmp, test命令: 比較

cmp命令


記法何の略か動作
cmp␣ op1[, op2]compareop1op2の比較結果をフラグに格納(比較はsub命令を使用)

詳しい記法例の動作サンプルコード
cmp␣ imm, r/mcmpq $999, %raxsubq $999, %raxのフラグ変化のみ計算.オペランドは変更なしcmp-1.s cmp-1.txt
cmp␣ r, r/mcmpq %rax, (%rsp)subq %rax, (%rsp)のフラグ変化のみ計算.オペランドは変更なしcmp-1.s cmp-1.txt
cmp␣ r/m, rcmpq (%rsp), %raxsubq (%rsp), %raxのフラグ変化のみ計算.オペランドは変更なしcmp-1.s cmp-1.txt

CFOFSFZFPFAF
!!!!!!
  • cmp命令はフラグ計算だけを行います. (レジスタやメモリは変化しません).
  • cmp命令は条件付きジャンプ命令と一緒に使うことが多いです. 例えば以下の2命令で「%raxが(符号あり整数として)1より大きければジャンプする」という意味になります.
cmpq $1, %rax
jg L2

test命令


記法何の略か動作
test␣ op1[, op2]logical compareop1op2の比較結果をフラグに格納(比較はand命令を使用)

詳しい記法例の動作サンプルコード
test␣ imm, r/mtestq $999, %raxandq $999, %raxのフラグ変化のみ計算.オペランドは変更なしtest-1.s test-1.txt
test␣ r, r/mtestq %rax, (%rsp)andq %rax, (%rsp)のフラグ変化のみ計算.オペランドは変更なしtest-1.s test-1.txt
test␣ r/m, rtestq (%rsp), %raxandq (%rsp), %raxのフラグ変化のみ計算.オペランドは変更なしtest-1.s test-1.txt

CFOFSFZFPFAF
00!!!?
  • cmp命令と同様に,test命令はフラグ計算だけを行います. (レジスタやメモリは変化しません).
  • cmp命令と同様に,test命令は条件付きジャンプ命令と一緒に使うことが多いです. 例えば以下の2命令で「%raxが0ならジャンプする」という意味になります.
testq %rax, %rax
jz L2
  • 例えば%raxが0かどうかを知りたい場合, cmpq $0, %raxtestq %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, op2movsx op2, op1
movsxd op2, op1
move with sign-extentionop1を符号拡張した値をop2に格納
movz␣␣ op1, op2movzx op2, op1move with zero-extentionop1をゼロ拡張した値をop2に格納

詳しい記法例の動作サンプルコード
movs␣␣ r/m, rmovslq %eax, %rbx%rbx = %eaxを8バイトに符号拡張した値movs-movz.s movs-movz.txt
movz␣␣ r/m, rmovzwq %ax, %rbx%rbx = %axを8バイトにゼロ拡張した値movs-movz.s movs-movz.txt

␣␣に入るもの何の略か意味
bwbyte to word1バイト→2バイトの拡張
blbyte to long1バイト→4バイトの拡張
bqbyte to quad1バイト→8バイトの拡張
wlword to long2バイト→4バイトの拡張
wqword to quad2バイト→8バイトの拡張
lqlong to quad4バイト→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形式)
例の動作サンプルコード
cbtwcbwcbtw%al(byte)を%ax(word)に符号拡張cbtw.s cbtw.txt
cwtlcwdecwtl%ax(word)を%eax(long)に符号拡張cbtw.s cbtw.txt
cwtdcwdcwtd%ax(word)を%dx:%ax(double word)に符号拡張cbtw.s cbtw.txt
cltdcdqcltd%eax(long)を%edx:%eax(doube long, quad)に符号拡張cbtw.s cbtw.txt
cltqcdqecltd%eax(long)を%rax(quad)に符号拡張cbtw.s cbtw.txt
cqtocqocqto%rax(quad)を%rdx:%rax(octuple)に符号拡張cbtw.s cbtw.txt

  • cqtoなどはidivで割り算する前に使うと便利(%rdx:%raxidivの隠しオペランドなので).
  • GNUアセンブラはIntel形式のニモニックも受け付ける.

ジャンプ命令

jmp: 無条件ジャンプ


記法何の略か動作
jmp op1jumpop1にジャンプ

詳しい記法例の動作サンプルコード
jmp reljmp 0x10000x1000番地に相対直接ジャンプ (%rip += 0x1000)jmp.s jmp.txt
jmp foofoo番地に相対直接ジャンプ (%rip += foo)jmp.s jmp.txt
jmp r/mjmp *%rax*%rax番地に絶対間接ジャンプ (%rip = *%rax))jmp.s jmp.txt
jmp r/mjmp *(%rax)*(%rax)番地に絶対間接ジャンプ (%rip = *(%rax))jmp.s jmp.txt
---
  • x86-64では,相対・直接と絶対・間接の組み合わせしかありません. (つまり,相対・間接ジャンプや絶対・直接ジャンプはありません. なお,ここで紹介していないfarジャンプでは絶対・直接もあります).
  • 相対・直接ジャンプでは符号ありの8ビット(rel8)か 32ビット(rel32)の整数定数で相対アドレスを指定します. (64ビットの相対アドレスは指定できません.64ビットのジャンプをしたい時は 絶対・間接ジャンプ命令を使います).
  • rel8rel32かはアセンブラが勝手に選んでくれます. 逆にjmpbjmplなどとサフィックスをつけて指定することはできません.
  • なぜか,定数なのにrel8rel32にはドルマーク$をつけません. 逆にr/mの前にはアスタリスク*が必要です. GNUアセンブラのこの部分は一貫性がないので要注意です.

条件付きジャンプの概要

  • 条件付きジャンプ命令 j␣は  ステータスフラグ (CF, OF, PF, SF, ZF)をチェックして, 条件が成り立てばジャンプします.
  • 条件付きジャンプは比較命令と一緒に使うことが多いです. 例えば以下の2命令で「%raxが(符号あり整数として)1より大きければジャンプする」という意味になります.
cmpq $1, %rax
jg L2
  • 条件付きジャンプ命令のニモニックでは次の用語を使い分けます
    • 符号あり整数の大小には less/greater を使う
    • 符号なし整数の大小には above/below を使う

条件付きジャンプ: 符号あり整数用


記法何の略か動作ジャンプ条件
jg rel
jnle rel
jump if greater
jump if not less nor equal
op2>op1ならrelにジャンプ
!(op2<=op1)ならrelにジャンプ
ZF==0&&SF==OF
jge rel
jnl rel
jump if greater or equal
jump if not less
op2>=op1ならrelにジャンプ
!(op2<op1)ならrelにジャンプ
SF==OF
jle rel
jng rel
jump if less or equal
jump if not greater
op2<=op1ならrelにジャンプ
!(op2>op1)ならrelにジャンプ
ZF==1||SF!=OF
jl rel
jnge rel
jump if less
jump if not greater nor equal
op2<op1ならrelにジャンプ
!(op2>=op1)ならrelにジャンプ
SF!=OF

詳しい記法例の動作サンプルコード
jg relcmpq $0, %rax
jg foo
if (%rax>0) goto foojg.s jg.txt
jnle relcmpq $0, %rax
jnle foo
if (!(%rax<=0)) goto foojg.s jg.txt
jge relcmpq $0, %rax
jge foo
if (%rax>=0) goto foojge.s jge.txt
jnl relcmpq $0, %rax
jnl foo
if (!(%rax<0)) goto foojge.s jge.txt
jle relcmpq $0, %rax
jle foo
if (%rax<=0) goto foojle.s jle.txt
jng relcmpq $0, %rax
jng foo
if (!(%rax>0)) goto foojle.s jle.txt
jl relcmpq $0, %rax
jl foo
if (%rax<0) goto foojl.s jl.txt
jnge relcmpq $0, %rax
jnge foo
if (!(%rax>=0)) goto foojl.s jl.txt

  • op1op2 は条件付きジャンプ命令の直前で使用したcmp命令のオペランドを表します.
  • jgjnleは異なるニモニックですが動作は同じです. その証拠にジャンプ条件はZF==0&&SF==OFと共通です. 他の3つのペア,jgejnljlejngjljngeも同様です.

条件付きジャンプ: 符号なし整数用


記法何の略か動作ジャンプ条件
ja rel
jnbe rel
jump if above
jump if not below nor equal
op2>op1ならrelにジャンプ
!(op2<=op1)ならrelにジャンプ
CF==0&ZF==0
jae rel
jnb rel
jump if above or equal
jump if not below
op2>=op1ならrelにジャンプ
!(op2<op1)ならrelにジャンプ
CF==0
jbe rel
jna rel
jump if below or equal
jump if not above
op2<=op1ならrelにジャンプ
!(op2>op1)ならrelにジャンプ
CF==1&&ZF==1
jb rel
jnae rel
jump if below
jump if not above nor equal
op2<op1ならrelにジャンプ
!(op2>=op1)ならrelにジャンプ
CF==1

詳しい記法例の動作サンプルコード
ja relcmpq $0, %rax
ja foo
if (%rax>0) goto fooja.s ja.txt
jnbe relcmpq $0, %rax
jnbe foo
if (!(%rax<=0)) goto fooja.s ja.txt
jae relcmpq $0, %rax
jae foo
if (%rax>=0) goto foojae.s jae.txt
jnb relcmpq $0, %rax
jnb foo
if (!(%rax<0)) goto foojae.s jae.txt
jbe relcmpq $0, %rax
jbe foo
if (%rax<=0) goto foojbe.s jbe.txt
jna relcmpq $0, %rax
jna foo
if (!(%rax>0)) goto foojbe.s jbe.txt
jb relcmpq $0, %rax
jb foo
if (%rax<0) goto foojb.s jb.txt
jnae relcmpq $0, %rax
jnae foo
if (!(%rax>=0)) goto foojb.s jb.txt

  • op1op2 は条件付きジャンプ命令の直前で使用したcmp命令のオペランドを表します.
  • jajnbeは異なるニモニックですが動作は同じです. その証拠にジャンプ条件はCF==0&&ZF==0と共通です. 他の3つのペア,jaejnbjbejnajbjnaeも同様です.

条件付きジャンプ: フラグ用


記法何の略か動作ジャンプ条件
jc reljump if carryCF==1ならrelにジャンプCF==1
jnc reljump if not carryCF==0ならrelにジャンプCF==0
jo reljump if overflowOF==1ならrelにジャンプOF==1
jno reljump if not overflowOF==0ならrelにジャンプOF==0
js reljump if signSF==1ならrelにジャンプSF==1
jns reljump if not signSF==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 reljc fooif (CF==1) goto foojc.s jc.txt
jnc reljnc fooif (CF==0) goto foojc.s jc.txt
jo reljo fooif (OF==1) goto foojo.s jo.txt
jno reljno fooif (OF==0) goto foojo.s jo.txt
js reljs fooif (SF==1) goto foojs.s js.txt
jns reljns fooif (SF==0) goto foojs.s js.txt
jz reljz fooif (ZF==1) goto foojz.s jz.txt
je relcmpq $0, %rax
je foo
if (%rax==0) goto foojz.s jz.txt
jnz reljnz fooif (ZF==0) goto foojz.s jz.txt
jne relcmpq $0, %rax
jne foo
if (%rax!=0) goto foojz.s jz.txt
jp reljp fooif (PF==1) goto foojp.s jp.txt
jpe reljpe fooif (PF==1) goto foojp.s jp.txt
jnp reljnp fooif (PF==0) goto foojp.s jp.txt
jpo reljpo fooif (PF==0) goto foojp.s jp.txt

  • op1op2 は条件付きジャンプ命令の直前で使用したcmp命令のオペランドを表します.
  • jzjeは異なるニモニックですが動作は同じです. その証拠にジャンプ条件はZF==1と共通です. 他の3つのペア,jnzjnejpjpejnpjpoも同様です.
  • AFフラグのための条件付きジャンプ命令は存在しません.

関数呼び出し(コール命令)

call, ret命令: 関数を呼び出す,リターンする


記法何の略か動作
call op1call procedure%ripをスタックにプッシュしてから op1にジャンプする
(pushq %rip; %rip = op1)
retreturn from procedureスタックからポップしたアドレスにジャンプする
(popq %rip)

詳しい記法例の動作サンプルコード
call relcall foo相対・直接の関数コールcall.s call.txt
call r/mcall *%rax絶対・間接の関数コールcall.s call.txt
retret関数からリターンcall.s call.txt

enter, leave命令: スタックフレームを作成する,破棄する


記法何の略か動作
enter op1, op2make stack frameサイズop1のスタックフレームを作成する
leavediscard stack frame今のスタックフレームを破棄する

詳しい記法例の動作サンプルコード
enter imm16, imm8enter $0x20, $0pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
enter.s enter.txt
leaveleavemovq %rbp, %rsp
popq %rbp
enter.s enter.txt

  • enter命令のop2には関数のネストレベルを指定するのですが, C言語では入れ子の関数がない(つまりネストレベルは常にゼロ)なので 常にゼロを指定します.
  • ただし,enterは遅いので通常は使いません. 代わりに同等の動作をするpushq %rbp; movq %rsp, %rbp; subq $n, %rspを使います.

その他

nop命令


記法何の略か動作
nopno operation何もしない(プログラムカウンタのみ増加)
nop op1no operation何もしない(プログラムカウンタのみ増加)

詳しい記法例の動作サンプルコード
nopnop何もしないnop.s nop.txt
nop r/mnopl (%rax)何もしないnop2.s nop2.txt

  • 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, op2compare and exchange%raxop2を比較し,同じならop2=op1,異なれば %rax=op2

詳しい記法例の動作サンプルコード
cmpxchg r, r/mcmpxchg %rbx, (%rsp)if (*(%rsp)==%rax) *(%rsp)=%rbx;
else %rax=*(%rsp);
cmpxchg.s cmpxchg.txt

CFOFSFZFPFAF
!!!!!!
  • cmpxchg命令などのCAS命令は,lock-free,つまりロックを使わず 同期機構を実現するために使われます. アトミックに実行する必要があるため,通常,LOCKプリフィックスをつけて使います.
  • 気持ち:
    • あるメモリにあるop2を新しい値op1で書き換えたい.
    • ただし,代入前のop2の値は%raxと同じはずで, もし(割り込まれて)知らない間に別の値になっていたら,この代入は失敗させる.
    • 代入が失敗したことを知るために, (他の誰かが更新した最新の)op2の値を%raxに入れる. cmpxchg実行後に%raxの値を調べれば,無事にop1への代入ができたかどうかが分かる.

cmpxchg8b, cmpxchg16b命令


記法何の略か動作
cmpxchg8b op1compare and exchange bytes%edx:%eaxop1を比較し,同じならop1=%ecx:%ebx,異なれば %edx:%eax=op1
cmpxchg16b op1compare and exchange bytes%rdx:%raxop1を比較し,同じならop1=%rcx:%rbx,異なれば %rdx:%rax=op1

詳しい記法例の動作サンプルコード
cmpxchg8b m64cmpxchg8b (%rsp)if (*(%rsp)==%edx:%eax) *(%rsp)=%ecx:%ebx;
else %edx:%eax=*(%rsp);
cmpxchg8b.s cmpxchg8.txt
cmpxchg16b m128cmpxchg16b (%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命令: タイムスタンプを読む


記法何の略か動作
rdtscread time-stamp counter%edx:%eax = 64ビットタイムスタンプカウンタ
rdtscpread time-stamp counter and processor ID%edx:%eax = 64ビットタイムスタンプカウンタ
%ecx = 32ビットプロセッサID

詳しい記法例の動作サンプルコード
rdtscrdtsc%edx:%eax = 64ビットタイムスタンプカウンタrdtsc.s rdtsc.txt
rdtscprdtscp%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も取得できます. rdtscrdtscpではシリアライズ処理が異なるため,得られるサイクル数も異なります. 詳しくは x86-64のマニュアルSDM を参照して下さい.

int3命令


記法何の略か動作
int3call to interrupt procedureブレークポイントトラップを発生

詳しい記法例の動作サンプルコード
int3int3ブレークポイントトラップを発生int3.s int3.txt

  • int3命令はブレークポイントトラップ(ソフトウェア割り込みの一種)を発生させます. 通常実行ではint3を実行した時点でプロセスは強制終了となりますが, デバッガ上ではその時点でブレークします.continueコマンドでその後の実行も継続できます.ブレークしたい場所が分かっている場合は, Cコード中にasm ("int3");と書くことでデバッガ上でブレークさせることができます.

ud2命令


記法何の略か動作
ud2undefined instruction無効オペコード例外を発生させる

詳しい記法例の動作サンプルコード
ud2ud2無効オペコード例外を発生させるud2.s ud2.txt

  • ud2命令は無効オペコード例外を発生させます. 通常実行ではud2を実行した時点でプロセスは, シグナルSIGILL (illegal instruction)を受け取り,強制終了となります デバッガ上でも, Program received signal SIGILL, Illegal instruction. というメッセージが出て,プロセスは終了になります. 本書では「実行が通るはずがない場所が本当かどうか」の確認のため ud2を使います.(通るはずがない場所にud2を置いて,SIGILLが発生しなければOKです)
例外 (exception)とは

例外(exception)はCPUが発生させる割り込み(ソフトウェア割り込み)です. Intel用語で,例外はさらにフォールト(fault),トラップ(trap), アボート(abort)に分類されます. 例えばゼロ割はフォールト,ブレークポイントはトラップです. マイOS作りたい人は頑張って勉強して下さい.

endbr64命令


記法何の略か動作
endbr64end branch 64 bit間接ジャンプ先として許す

詳しい記法例の動作サンプルコード
endbr64endbr64間接ジャンプ先として許す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␣ op1set byte on conditionif (条件␣が成立) op1=1; else op1=0;

詳しい記法例の動作サンプルコード
set␣ r/m8setz %al%al = ZFsetz.s setz.txt
setg %al より大きい(greater)条件が成立なら%al =1,違えば%al =0setg.s setg.txt

  • set␣命令はステータスフラグの値を取得します. には条件付きジャンプ命令j␣と同じものをすべて入れられます.

ストリング命令

movsなどのストリング命令はREPプリフィクスと組み合わせて使います.

  • REPプリフィクス

記法何の略か動作
rep insnrepeat%ecx==0になるまで
命令insn%ecx--を繰り返し実行
repe insnrepeat while equal%ecx==0またはフラグZF==0になるまで
命令insn%ecx--を繰り返し実行
repz insnrepeat while zero
repne insnrepeat while not equal%ecx==0またはフラグZF==1になるまで
命令insn%ecx--を繰り返し実行
repnz insnrepeat while not zero

詳しい記法例の動作サンプルコード
rep insnrep movsbwhile (%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)%raxnバイト転送; %rsi += n;
    stos␣store string%rax(%rdi)nバイト転送; %rdi += n;
    ins␣input stringI/Oポート%dxから(%rdi)nバイト転送; %rdi += n;
    outs␣output string(%rsi)からI/Oポート%dxnバイト転送; %rsi += n;
    cmps␣compare string(%rsi)(%rdi)nバイト比較; %rsi += n; %rdi += n;
    scas␣scan string%rax(%rdi)nバイト比較; %rdi += n;
    詳しい記法例の動作サンプルコード
    rep movsbrep movsbwhile (%ecx-- > 0) (*%rdi++) = (*%rsi++);
    # 1バイトずつコピー
    rep.s rep.txt
    rep lodsbrep lodsbwhile (%ecx-- > 0) %al = (*%rsi++);
    # 1バイトずつコピー
    lods.s lods.txt
    rep stosqrep stosqwhile (%ecx-- > 0) {(*%rdi) = %rax; %rdi+=8; }
    # 8バイトずつコピー
    stos.s stos.txt
    repe cmpsbrepe cmpsbwhile (%ecx-- > 0 && (*%rdi++) == (*%rsi++);
    # 1バイトずつ比較
    repe.s repe.txt
    repne scasbrepne scasbwhile (%ecx-- > 0 && (*%rdi++) != %rax);
    # 1バイトずつ比較
    scas.s scas.txt
    • にはbwlqが入り,それぞれ, メモリ参照のサイズ(上ではnと表記)が1バイト,2バイト,4バイト,8バイトになる. (ただし,insoutsbwlのみ指定可能).
    • %raxはオペランドサイズにより,%rax%eax%ax%alのいずれかになる.
    • ins␣out␣の実例はここでは無し.
  • DFフラグ(方向フラグ)とcld命令・std命令

    記法何の略か動作
    cldclear direction flagDF=0
    stdset direction flagDF=1
    詳しい記法例の動作サンプルコード
    cldcldDF=0cld.s cld.txt
    stdstdDF=1cld.s cld.txt
    • DFフラグはストリング命令で,%rsi%rdiを増減する方向を決めます.
      • DF=0 の時は%rsi%rdiを増やします
      • DF=1 の時は%rsi%rdiを減らします
    • DFフラグの変更はcldstdで行います (一般的にフラグレジスタの値を変更する場合,pushfでフラグレジスタの値を保存し, popfで元に戻すのが安全です).
    • Linux AMD64のABIにより, 関数の出入り口ではDF=0に戻す必要があります.このお約束のため, 自分でstdしていなければ,必ずDF==0となります(わざわざcldする必要はありません).