データ定義 (3)

データセクション
前回は、コードセクション (.text) でデータを定義しましたが、通常、データは、「.data」や「.rodata」などのセクションに出力します。

.data初期化されているデータ。
読み書き可能で、初期値が必要なグローバル変数として使う。
.rodata
.rdata
読み込み専用データ。
(Unix/MacOS の場合は .rodata。PE の場合は .rdata)
.bss初期化されていないデータ。
(実行時に確保され、0 で初期化される)
読み書き可能であるが、初期値が必要ない、または 0 で初期化するグローバル変数として使う。
サンプルコード (1)
.data と .rodata セクションを使ったサンプルを実行してみます。
今回も、C ソースの方は test1.c を使います。

<07a_data.asm>
default rel
global testfunc

; .rodata
section .rodata

rdata: dd 100

; .data
section .data

rwdata: dd 200

; コード
section .text

testfunc:
    mov ecx, [rdata]  ; ECX = 100
    add ecx, [rwdata] ; ECX = 100+200 = 300
    mov [rwdata], ecx ; [rwdata] = ECX
    mov eax, [rwdata] ; EAX = [rwdata] (300)
    ret

$ nasm -f elf64 07a_data.asm
$ cc -o test test1.c 07a_data.o

$ ./test
300
解説
default rel で、RIP 相対アドレスを使うことを指定します。

rdata のラベル位置に、読み込み専用データ (.rodata) として、32bit 値の 100 を定義。
rwdata のラベル位置に、読み書き可能データ (.data) として、32bit 値の 200 を定義します。

まず、ECX レジスタに、rdata ラベル位置の 100 の値を読み込み、その後、ADD 命令で、ECX に rwdata ラベル位置の 200 の値を加算します。
この時点で、ECX レジスタの値は 300 です。

その ECX レジスタの値を、一旦 rwdata のラベル位置に書き込みます (読み書きが可能であることを確認するため)。
その後、rwdata のラベル位置から、EAX に値を読み込みます。

結果として、300 の値が、戻り値として返ります。

ちなみに、読み込み専用である、rdata のラベル位置に値を書き込もうとすると、Segmentation fault になります。
サンプルコード (2)
次は、.bss セクションを使ったサンプルです。

<07b_bss.asm>
default rel
global testfunc

; .bss
section .bss

data1: resd 2
data2: resd 1

; コード
section .text

testfunc:
    mov dword [data1], 10
    mov dword [data1+4], 20
    mov eax, [data1]   ; EAX = 10
    add eax, [data1+4] ; EAX = 10+20 = 30
    add eax, [data2]   ; EAX = 30+0 = 30
    ret

$ nasm -f elf64 07b_bss.asm
$ cc -o test test1.c 07b_bss.o

$ ./test
30
解説
.bss セクションは、セクション全体のサイズが情報として記録されるだけで、データとしては何も出力されません。
BSS セクションにデータ領域を定義する場合は、RESx 疑似命令を使います。

BSS セクションのデータは、プログラムの実行時に確保され、0 で初期化されます。
読み書き可能な領域なので、確保された後は、プログラム側で値を書き込んだり、読み込んだりすることができます。

上記のプログラムの場合、data1 の位置に 32bit x 2、data2 の位置に 32bit x 1 を確保しています。

testfunc 関数内では、data1 の位置に 10 の値を書き込み、data1 + 4 の位置に 20 の値を書き込んでいます。
(オフセット値はバイト単位で指定します)

MOV 命令で、メモリ位置に即値を代入する場合、mov [data1], 10 だけでは、実際にどのサイズの値を書き込むかが判断できないため、アドレス指定の前に dword (32bit) のキーワードを付けて、明示的にサイズを指定する必要があります。

data2 の位置には何も書き込んでいませんが、領域は確保時に 0 で初期化されているため、値は 0 です。

data1: 10, 20
data2: 0

次に、EAX レジスタに data1 の位置の値を読み込み (10)、さらに、ADD 命令で、data1 + 4 の位置から 20 の値を読み込んで加算し (10 + 20 = 30)、その後、data2 の位置から 0 の値を読み込んで加算します (30 + 0 = 30)。

結果、EAX の値は 30 となるので、戻り値は 30 です。
GLOBAL/EXTERN
同じソースファイル内で定義されたデータであれば、普通に参照できますが、別のオブジェクトファイルで定義されたデータ (関数も同様) を参照したい場合は、GLOBALEXTERN を使って、シンボル名を宣言する必要があります。

GLOBAL は、自ソース内で定義されたシンボルを、他のオブジェクトから参照できるようにするために宣言します。
EXTERN は、他のオブジェクトで定義されたシンボルを、自ソース内で使用できるようにするために宣言します。

データを参照する側:
extern gdata
; 他のオブジェクトで定義された gdata を参照できるように宣言

section .text

testfunc:
    mov eax, [rel gdata]
    ret

データを参照される側:
global gdata
; gdata を他のオブジェクトから参照できるようにするため、宣言

section .data

gdata: dd 12

カンマ (,) で区切ることで、複数のシンボルを指定することもできます。
追加の情報
GLOBAL や EXTERN でシンボルを宣言する際、シンボル名の後にコロン (:) を付けて、出力形式固有の単語を続けると、そのシンボルに対する、追加の情報を指定できます。

出力形式が ELF の場合は、シンボルが、関数またはデータのいずれであるかを明確に指定できます。

global symbol_func:function
global symbol_data:data

function の場合は関数、data または object の場合は、データのシンボルとして定義されます。

この情報は、ELF のシンボルテーブルに、シンボルタイプとしてセットされます。
デフォルトでは、タイプは「なし」になります。

実行ファイルの動作自体には影響しませんが、必要であれば指定しておくと良いでしょう。

シンボルテーブル
例えば、readelf コマンドで、07a_data.o のシンボルテーブルを表示すると、rdata, rwdata, testfunc の各シンボルのタイプは、いずれも NOTYPE になっています。

$ readelf -s 07a_data.o

Symbol table '.symtab' contains 8 entries:
  番号:      値         サイズ タイプ  Bind   Vis      索引名
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS ...
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 .rodata
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 .data
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 .text
     5: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT    1 rdata
     6: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT    2 rwdata
     7: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT    3 testfunc

07a_data.asm の「global testfunc」を「global testfunc:function」に置き換えてコンパイルすると、タイプが FUNC に変わります。

     7: 0000000000000000     0 FUNC    GLOBAL DEFAULT    3 testfunc