データ定義 (1)

データ定義
アセンブラ内で、文字列や数値配列といったデータを定義して使用したい場合、Dx 疑似命令を使って、初期化されているデータセクション (.data や .rodata) に対して、バイナリを出力する必要があります。

データを任意の値で初期化する必要がなく、グローバル変数として、読み書き可能な、固定サイズのデータ領域が必要な場合は、RESx 疑似命令を使って、初期化されていないデータセクション (.bss) に対して、データ領域を確保する必要があります。
Dx 疑似命令
Dx (以下の疑似命令の総称) を使ってデータを定義すると、現在のセクションに、値のバイナリが出力されます。
※疑似命令は、CPU の命令と同じような形で扱われますが、NASM 側で処理される命令です。

DBBYTE (1byte)
DWWORD (2byte)
DDDWORD (4byte)
整数または単精度浮動小数点数
DQQWORD (8byte)
整数または倍精度浮動小数点数
DTTWORD (10byte:80bit)
x87 の拡張倍精度浮動小数点数
DOOWORD (16byte:128bit)
DYYWORD (32byte:256bit)
DZZWORD (64byte:512bit)

定義するデータのサイズごとに、命令の名前が異なります。
上記の命令の後に空白を記述した後、整数/浮動小数点数/文字列などの値を記述します。
値をカンマ (,) で区切ると、連続して複数個定義できます。

※浮動小数点数を指定する場合は、必ず '.' を含める必要があります。

db 0x55                ; [1byte]
db 0x55,0x56,0x57      ; [byte x 3]
dw 0x1234              ; [word] 34 12
dd 0x12345678          ; [dword] 78 56 34 12
dq 0x123456789abcdef0  ; [64bit 数値]
dd 1.0                 ; [32bit 単精度]
dq 1.234567            ; [64bit 倍精度]
dt 1.234567            ; [拡張倍精度浮動小数点数 (80bit)]

リトルエンディアンで出力されるため、2 byte 以上のサイズの数値は、下位バイトから順に出力されます。
RESx 疑似命令
BSS セクション (.bss) で、初期化されていないデータ領域 (実行時に 0 で初期化されるデータ) を定義する場合は、以下を使います。

RESB、RESW、RESD、RESQ、REST、RESO、RESY、RESZ

Dx と同じように、データのサイズごとに命令の名前が異なります。

命令の後に空白を開け、次に、確保するデータの数を、数値で指定します。

resb  64 ; 1byte x 64
resw  4  ; 2byte x 4
resd  8  ; 4byte x 8

このデータ領域は、実行時に確保されるものであり、出力ファイルには、BSS セクション全体のサイズだけが記録されます。
実際にサイズ分のバイナリデータが出力されるわけではありません。
コードとデータ
Dx 疑似命令は、コードセクションやデータセクションにかかわらず、どのセクションでも、同じように使用することができます。

CPU 命令にしても、Dx 疑似命令にしても、現在のセクションに、アセンブラコードに対応したバイナリを出力するという点では違いはないので、コードセクションで Dx 命令を使ったり、データセクションで CPU 命令を記述することもできます。

実際、02_test.asm を以下の内容に置き換えて、バイナリコードを直接出力しても、実行ファイルは問題なく動作します。

global testfunc
section .text

testfunc:
    db 0xb8,0x7b,0x00,0x00,0x00,0xc3
文字列
文字列をデータとして定義したい場合は、Dx 疑似命令内で、文字定数と同じように、「' " `」のいずれかで文字列を囲みます。

この場合、1〜8文字という制限はなく、長い文字列を使うことができますが、文字列を数値として扱うという点では変わりません。
文字列の先頭から順に、Dx 命令のサイズ分のバイトを数値として読み込み、それを出力する形になります。

通常の文字列として扱いたい場合は、DB (1byte) を使って定義します。

db 'abcde',0 ; (1byte) 61 62 63 64 65 00
dw 'abcde'   ; (2byte) 61 62 63 64 65 00
dd 'abcde'   ; (4byte) 61 62 63 64 65 00 00 00

2 byte 以上のサイズで定義した場合
DW (2byte) で文字列を定義した場合は、2 byte 単位で文字列を扱うことになります。

'ab' -> 0x6261 -> 61 62
'cd' -> 0x6463 -> 63 64
'e'  -> 0x0065 -> 65 00

最初の文字から順に、2 byte 整数として値を読み込み、それを 2 byte 整数として出力します。

結果的に、先頭の文字から順に、そのまま同じ順番で文字列が出力される形になりますが、最後に残った文字が、指定サイズに達しない場合、残りの文字は 0 として扱われます。

そのため、文字列全体を N バイト境界の文字列として定義したい場合は、DW や DD を使って、自動で余白の 0 のバイトを出力することができます。
Unicode 文字列
データとして、UTF-16 または UTF-32 の文字列を定義したい場合は、以下の特殊演算子を使用します。

__?utf16?__
__?utf16le?__
__?utf16be?__
__?utf32?__
__?utf32le?__
__?utf32be?__

() 内で UTF-8 の文字列を受け取り、その文字列を、UTF-16 または UTF-32 (2byte または 4byte の整数配列) に変換します。
le はリトルエンディアン、be はビッグエンディアンです。
指定がない場合、デフォルトは、リトルエンディアンです。

; あ = U+3042, お = U+304a

dw __?utf16?__('あお')   ; [UTF-16] 42 30 4a 30
dd __?utf32be?__('あお') ; [UTF-32BE] 00 00 30 42 00 00 30 4a

アセンブラのソースファイルが UTF-8 エンコーディングの場合、' " ` で囲まれた文字列は、そのまま UTF-8 文字列になります。
そのため、直接 UTF-8 文字列を記述して、UTF-16 や UTF-32 に変換することができます。

なお、これらを頻繁に使用する場合、毎回記述するのは面倒なので、マクロで別名定義しておくと便利になります。

%define u(x) __?utf16?__(x)
%define w(x) __?utf32?__(x)

dw u('あお')
TIMES 疑似命令
データ定義または CPU 命令の前に times [数値] を付けると、以降の命令を指定回数繰り返すことができます。

主にデータ定義で使いますが、CPU 命令に対して使うことも出来ます。

times 10 db 5 ; (1byte) 05 を 10個 定義

times 3 nop   ; NOP 命令を3回
INCBIN
INCBIN でファイル名を指定すると、指定されたファイルの内容を読み込み、バイナリデータとしてそのまま出力します。

NASM の -i/-I オプションで指定された、インクルードファイル検索パスが有効になります。

section .rodata

incbin "file.dat"           ; 全サイズ
incbin "file.dat",1024      ; 先頭 1024 byte をスキップ
incbin "file.dat",1024,512  ; 先頭 1024 byte をスキップ。最大 512 byte

ファイル名の後に、カンマで区切って、1〜2つの数値を指定すると、「先頭のスキップするバイト数」と「読み込む最大バイト数」を指定できます。
境界合わせ
ALIGNALIGNB マクロを使うと、コードやデータの次の位置を、指定バイト数の境界に合わせることができます。

実際の処理としては、次の命令やデータの位置を、指定された境界位置に合わせるため、余分なバイトを出力するための命令に置き換えます。

例えば、4 byte の数値を指定アドレスから読み込みたい場合、そのアドレスが 4 の倍数になっていないと、パフォーマンスが落ちる場合があります。
また、アライメントが強制されている SSE などの命令を使って、メモリから読み書きしたい場合は、アドレスが特定のサイズの倍数になっている必要があります。

境界合わせは、自動で行われることはないので、必要に応じて、手動で行う必要があります。
使い方
ALIGNx の1番目の引数は、合わせたい境界のバイト数です。
カンマで区切った2番目の引数では、追加の余分なバイトを埋めるための命令を指定します (実際は、その前に times N が付けられます)。

2番目の引数を省略した場合、ALIGN のデフォルトは NOP (何もしない CPU 命令。90h) で、
ALIGNB のデフォルトは RESB 1 (初期化されていない 1 byte データ領域の定義) です。

例えば、現在位置 (セクション先頭からの位置) が 1 の箇所に ALIGN 4 を記述した場合、次の位置を 4 にする必要があるので、この位置に、追加で 3 byte の余分なバイトを出力する必要があります。
ALIGN のデフォルトの命令は NOP なので、NOP を3回出力するため、ALIGN 4 は times 3 nop に置き換わることになります。

; 次の位置が 4byte 境界になるように、NOP バイト (90h) を追加
; (times N nop)
align  4

; 8byte 境界 (0 で埋める)
; (times N db 0)
align  8, db 0

; 初期化されていないデータを 4byte 境界に
; (times N resb 1)
alignb 4

通常は、コードとデータのセクションでは ALIGN を使用し、BSS セクション (初期化されていないデータ) では ALIGNB を使用します。

データセクションで ALIGN を使用した場合、2番目の引数を省略すると、追加のバイトは 90h で埋められますが、何の値で埋めたとしても特に問題はないので、気にしないでください。
気になるのであれば、2番目の引数に db 0 を指定して、0 の値で埋めることもできます。