一般的な CPU アーキテクチャでは、
そのためアセンブラプログラムの多くは、
今回は、
アドレッシング
アセンブラでは、
冒頭で
各CPUアーキテクチャごとにさまざまな種類のアドレッシングが提供されていますが、
レジスタ指定/値指定
処理対象として、
Intel 80x86アーキテクチャでレジスタを指定する場合、
32ビットIntel 80x86アーキテクチャでは、
ただし、
値そのものが指定される場合、
.text
.align 4
.global entry_point
entry_point:
int3 # プログラム実行の一時停止
# 即値 ⇒ レジスタ eax
movl $0x12345678, %eax
# レジスタ eax ⇒ レジスタ ebx
movl %eax, %ebx
.global end_of_program
end_of_program:
int3 # プログラム実行の一時停止
nop
即値は値そのものですから、
# レジスタ ⇒ 即値
movl %eax, $0x12345678
# 即値 ⇒ 即値
movl $0xFFFFFFFF, $0x12345678
これまでは特に断りなく "movl"というデータ転送命令の表記を用いてきましたが、mov" とl"(long)を組み合わせたものです。
データ転送におけるデータ長を16ビットw" や "b"を指定します。
.text
.align 4
.global entry_point
entry_point:
int3 # プログラム実行の一時停止
movl $0x12345678, %eax
movl $0xFFFFFFFF, %ebx
movl $0xFFFFFFFF, %ecx
# 16 ビット長の転送
movw %eax, %ebx
# 8 ビット長の転送
movb %eax, %ecx
.global end_of_program
end_of_program:
int3 # プログラム実行の一時停止
nop
データ長を32ビット未満に限定した場合、
(gdb) run .... Program received signal SIGTRAP, Trace/breakpoint trap. 0x00401001 in entry_point () (gdb) continue Continuing. Program received signal SIGTRAP, Trace/breakpoint trap. 0x00401016 in end_of_program () (gdb) info register eax ebx ecx eax 0x12345678 305419896 ebx 0xffff5678 -43400 ecx 0xffffff78 -136 (gdb)
なお、
Warning: using `%bx' instead of `%ebx' due to `w' suffix Warning: using `%ax' instead of `%eax' due to `w' suffix Warning: using `%cl' instead of `%ecx' due to `b' suffix Warning: using `%al' instead of `%eax' due to `b' suffix
これは、%eax"がw"や"b"と整合が取れていないことに対する警告です。
Intel 80x86アーキテクチャでは、
| 表記 | データ長 | 意味 |
|---|---|---|
eax | 32ビット | axの32ビット拡張 |
ax | 16ビット | eax |
ah | 8ビット | ax |
al |
8 ビット | ax |
上記はEAXレジスタに対する表記例ですが、
直接アドレッシング/間接アドレッシング
取り扱い対象を直接指定するのが
以下に、
.data
.align 4
.global value
value:
.long 1
.text
.align 4
.global entry_point
entry_point:
int3 # プログラム実行の一時停止
# 転送元: value 領域の格納値(間接)
# 転送先: レジスタ eax(直接)
movl value, %eax
# 転送元: value 領域のアドレス(直接)
# 転送先: レジスタ eax(直接)
movl $value, %eax
# 転送元: レジスタ eax の参照先領域の格納値(間接)
# 転送先: レジスタ ebx(直接)
movl (%eax), %ebx
# 転送元: 即値(直接)
# 転送先: レジスタ eax の参照先領域の格納値(間接)
movl $0xFFFFFFFF, (%eax)
# 転送元: 即値(直接)
# 転送先: value 領域(間接)
movl $0x12345678, value
# 転送元: レジスタ eax(直接)
# 転送先: value 領域(間接)
movl %eax, value
.global end_of_program
end_of_program:
int3 # プログラム実行の一時停止
nop
上記サンプルプログラムを実行して、
(gdb) disassemble entry_point end_of_program
※ 逆アセンブルで命令位置を確認
Dump of assembler code from 0x401000 to 0x401022:
0x00401000 : int3
0x00401001 : mov 0x402000,%eax
0x00401006 : mov $0x402000,%eax
0x0040100b : mov (%eax),%ebx
0x0040100d : movl $0xffffffff,(%eax)
0x00401013 : movl $0x12345678,0x402000
0x0040101d : mov %eax,0x402000
End of assembler dump.
(gdb) run
....
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00401001 in entry_point ()
(gdb) info register eax ebx
eax 0x0 0
ebx 0x7ffdf000 2147348480
(gdb) print/x value
$1 = 0x1
※ レジスタ/メモリ内容の初期値を確認
(gdb) print/x &value
$2 = 0x402000
※ value のアドレスを確認
(gdb) stepi
0x00401006 in entry_point ()
※ "mov 0x402000,%eax" の実行
(gdb) info register eax
eax 0x1 1
※ レジスタ値の確認(= value 位置の内容)
(gdb) stepi
0x0040100b in entry_point ()
※ "mov $0x402000,%eax" の実行
(gdb) info register eax
eax 0x402000 4202496
※ レジスタ値の確認(= value のアドレス)
(gdb) stepi
0x0040100d in entry_point ()
※ "mov (%eax),%ebx" の実行
(gdb) info register ebx
ebx 0x1 1
※ レジスタ値の確認(= value 位置の内容)
(gdb) stepi
0x00401013 in entry_point ()
※ "movl $0xffffffff,(%eax)" の実行
(gdb) print/x value
$3 = 0xffffffff
※ value 位置の内容確認
(gdb) stepi
0x0040101d in entry_point ()
※ "movl $0x12345678,0x402000" の実行
(gdb) print/x value
$4 = 0x12345678
※ value 位置の内容確認
(gdb) stepi
0x00401022 in end_of_program ()
※ "mov %eax,0x402000" の実行
(gdb) print/x value
$5 = 0x402000
※ value 位置の内容確認
(gdb)
GDBのstepiコマンドは、
あまり一般的ではありませんが、
アドレッシング修飾
アドレッシングを理解する上での大きな分類としては、
- 絶対
(absolute)/ 相対 (relative) -
たとえば、
処理対象を単独 (=絶対値とみなす) で利用する以外に、 他の値と組み合わせて (=相対値とみなす) 使用することも可能です。 - ディスプレースメント
(displacement) 指定 -
処理対象に対して、
即値を加えるアドレッシングは、 一般に 「ディスプレースメント指定」 と呼ばれます (相対アドレッシングの一種と言えます)。 通常、
指定された即値は符号拡張されますので、 「加える」 と表現してはいますが、 実際には加算対象を中心とする一定範囲を指す用途に使用されます。 この指定は、
C言語で言う構造体のフィールドアクセスや、 スタック領域の参照に使用されます。 - インデックス(index)指定
-
即値の加算を
「ディスプレースメント指定」 と呼ぶのに対して、 レジスタ値 (あるいはそれを元にした値) の加算は 「インデックス指定」 と呼ばれます (これも相対アドレッシングの一種と言えます)。 ディスプレースメント指定の場合と同様に、
加算対象を中心とする一定範囲を指す用途に使用されます。 この指定は、
配列要素のアクセスに使用されます。
なお、
詳細に関しては、
Intel 80x86アーキテクチャでは、
[ディスプレースメント](レジスタ[, インデックスレジスタ[, 倍数]])
括弧("[ ]")で囲まれた部位は省略可能です。
ディスプレースメント値自体はいわゆる即値ですが、
「倍数」
これらのアドレッシング修飾は、
.data
.align 4
.global value
value:
.long 1
.long 2
.long 3
.long 4
.text
.align 4
.global entry_point
entry_point:
int3 # プログラム実行の一時停止
movl $value, %eax
# 修飾無し(絶対)
movl (%eax), %ebx
# ディスプレースメント指定(相対)
movl 4(%eax), %ebx
# アクセス先アドレスは eax + 4 となり
# ebx には 2 が格納される
# インデックス指定(相対)
movl $2, %ecx
movl (%eax, %ecx, 4), %ebx
# アクセス先アドレスは eax + ecx(2) x 4 となり
# ebx には 3 が格納される
# ディスプレースメント・インデックス指定(相対)
movl $4, %ecx
movl -4(%eax, %ecx, 4), %ebx
# アクセス先アドレスは eax + ecx(4) x 4 - 4 となり
# ebx には 4 が格納される
.global end_of_program
end_of_program:
int3 # プログラム実行の一時停止
nop
上記の例では様々なアドレッシング修飾を用いて、
ここでたとえば、
Intel 80x86アーキテクチャでは、lea"
たとえば、mov" 命令と"lea" 命令は以下のように異なります。
movl -4(%eax, %ecx, 4), %ebx
# アクセス先アドレスは eax + ecx(4) x 4 - 4 となり
# ebx には 4 が格納される
leal -4(%eax, %ecx, 4), %ebx
# アドレッシング結果は eax + ecx(4) x 4 - 4 となり
# ebx には eax + 12 が格納される
メモリ上のアドレスをレジスタに格納する場合は、mov" 命令よりも"lea" 命令を用いるのが一般的ですので、lea" 命令を使用するようにします。
データ格納に関するあれこれ
バイトオーダー
Nビットの値において、
最上位ビットを含むバイト
0x12345678という32ビット値の例で言うと、
32ビットIntel 80x86アーキテクチャはLSB Firstのバイトオーダーを採用しています。
これらのバイトオーダーに注意を要するケースは、
しかし、
境界整合
メモリに対するデータ転送を行う場合、
たとえば、
前回~今回にかけて掲載しているサンプルプログラムにおいて記述されている.align指定は、
このようにアドレスをデータサイズの倍数に揃えることを、
ただし、
たとえば、
また、
しかし軽減されているとは言え、
メモリの前の平等
プログラム領域からの読み込み
これまでに例示してきたアセンブラソースでは、
しかし
.text
.align 4
.global entry_point
entry_point:
int3 # プログラム実行の一時停止
# entry_point から 4 バイトの読み込み
movl entry_point, %eax
.global end_of_program
end_of_program:
int3 # プログラム実行の一時停止
nop
リスト7のプログラムを実行してみましょう。
(gdb) run
....
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00401001 in entry_point ()
(gdb) x/8bx entry_point
※ entry_point からの 8 バイトをデータとみなして16進数表示
0x401000 <entry_point>: 0xcc 0xa1 0x00 0x10 0x40 0x00 0xcc 0x90
(gdb) info register eax
eax 0x0 0
(gdb) continue
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00401007 in end_of_program ()
(gdb) info register eax
eax 0x1000a1cc 268476876
(gdb)
"movl entry_" の実行により、entry_が指す領域に格納された0x1000a1ccが格納されました
なお、
ちなみに、
これは、int3をデバッガが埋め込むためです
デバッガがint3を埋め込んだ位置に対して、
「プログラム 」と「データ」
これまでは特に説明せずに.textや.dataなどとアセンブラソース中に記述してきましたが、
.textは、.dataは、
一般に、
最終的な所属セグメントが異なる以外は、.data に続いてプログラム形式で記述することも、.text に続いてデータ形式で記述することもできます。
.data
.align 4
.global program_as_data
program_as_data:
# データセグメントでプログラム形式の記述
movl $0x12345678, %eax
movl %eax, %ebx
.long 0xFFFFFFFF # ダンプ時の目印
.text
.align 4
.global entry_point
entry_point:
int3 # プログラム実行の一時停止
# テキストセグメントでデータ形式の記述
.byte 0xB8, 0x78, 0x56, 0x34, 0x12
.byte 0x89, 0xC3
.global end_of_program
end_of_program:
int3 # プログラム実行の一時停止
nop
GDBを使って、entry_から始まるデータがプログラムとして、program_から始まるプログラムがデータとして認識されていることを確認してみましょう。
(gdb) disassemble entry_point end_of_program Dump of assembler code from 0x401000 to 0x401008: 0x00401000 <entry_point+0>: int3 0x00401001 <entry_point+1>: mov $0x12345678,%eax 0x00401006 <entry_point+6>: mov %eax,%ebx End of assembler dump. (gdb) x/12bx &program_as_data 0x402000 <program_as_data>: 0xb8 0x78 0x56 0x34 0x12 0x89 0xc3 0xff 0x402008 <program_as_data+8>: 0xff 0xff 0xff 0x00 (gdb)
「プログラム」