アンティーク・アセンブラ~Antique Assembler

第2回 メモリに始まりメモリに終わる

この記事を読むのに必要な時間:およそ 8.5 分

メモリの前の平等

プログラム領域からの読み込み

これまでに例示してきたアセンブラソースでは,メモリとレジスタの間でのデータ転送は,常に「この領域はデータを格納するもの」とみなした領域との間で行ってきました。

しかし「メモリに格納されている」という点では,データもプログラムも同じですので,実はプログラムそのものを読み込むことも可能です。

リスト7 プログラム領域からの読み込み

    .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のプログラムを実行してみましょう。

図5 プログラム領域からの読み込み実行例

(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_point, %eax" の実行により,レジスタeaxにはentry_pointが指す領域に格納された0x1000a1ccが格納されました(先述したようにIntel 80x86アーキテクチャのバイトオーダーはLSB Firstですから,メモリ内容の表示とレジスタ値の表示を比較する際には順序の読み替えが必要です⁠⁠。

なお,⁠プログラムそのものを読み込む」とは言っても,レジスタ上に読み込んだ「プログラム」が実行できるわけではありません。単に「プログラムをデータとみなして読み込む」だけですのでご注意ください。

ちなみに,上記のプログラムを改変して読み込み位置を変化させてみると,想定していたものとは違うデータがレジスタeaxに読み込まれた,といった状況が発生するかもしれません。

これは,ブレークポイントを設定したり,step/stepi等による実行制御を行う際に,実行を中断するための命令int3をデバッガが埋め込むためです(この命令は16進数表記で0xccとなる長さ1バイトの命令です⁠⁠。

デバッガがint3を埋め込んだ位置に対して,メモリ内容表示や逆アセンブル表示が要求された場合には,埋め込み前の内容を元にした処理を行うので,一見想定外のデータが読み込まれたように見えてしまうのです。

「プログラム 」と「データ」

これまでは特に説明せずに.text.dataなどとアセンブラソース中に記述してきましたが,これらには重要な意味があります。

.textは,それ以後に記述された内容がプログラムとして実行可能なものであることを,.dataは,それ以後に記述された内容がプログラムとして実行されないもの,すなわちデータであることを意味します。

一般に,前者をテキストセグメント⁠text segment⁠⁠,後者をデータセグメント⁠data segment)と呼びます。

プログラム実行中に改変されることのない変数(C/C++で言うところのconst指定された変数)の領域は,⁠書き込み不可のデータセグメント」に含まれる場合と,⁠実行不可のテキストセグメント」に含まれる場合があるため,⁠テキストセグメントは実行可能」というのは必ずしも正しくありません。

これらに関する詳細を知りたい方には,オブジェクトファイルのリンケージや,プログラムのローディングに関する文献等にあたることをお勧めします。

最終的な所属セグメントが異なる以外は,アセンブラにとってプログラムとデータの間に差異はありませんから,.data に続いてプログラム形式で記述することも,.text に続いてデータ形式で記述することもできます。

リスト8 記述形式の入れ替え(0002-08)

    .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_pointから始まるデータがプログラムとして,program_as_dataから始まるプログラムがデータとして認識されていることを確認してみましょう。

図6 記述形式の入れ替え確認

(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)

GDBでデータセグメントのシンボル(先の例では program_as_dataを指定した場合,C言語で言うところの大域変数参照とみなされて,指定された領域に格納されている値が使用されます。

そのため上記の実行例では,program_as_dataが指すアドレス」ということを明示するために,"&" を使用しています。

「プログラム」「データ」がメモリ上において等価である,という感じが掴めたでしょうか?

著者プロフィール

藤原克則(ふじわらかつのり)

Mercurial三昧の日々が嵩じて, いつの間にやら『入門Mercurial Linux/Windows対応』を上梓。凝り性なのが災いして,年がら年中アップアップな一介の実装屋。最近は仕事の縁が元で,OpenSolarisに入れ込む毎日。