データ定義
アセンブラ内で、文字列や数値配列といったデータを定義して使用したい場合、Dx 疑似命令を使って、初期化されているデータセクション (.data や .rodata) に対して、バイナリを出力する必要があります。
データを任意の値で初期化する必要がなく、グローバル変数として、読み書き可能な、固定サイズのデータ領域が必要な場合は、RESx 疑似命令を使って、初期化されていないデータセクション (.bss) に対して、データ領域を確保する必要があります。
データを任意の値で初期化する必要がなく、グローバル変数として、読み書き可能な、固定サイズのデータ領域が必要な場合は、RESx 疑似命令を使って、初期化されていないデータセクション (.bss) に対して、データ領域を確保する必要があります。
Dx 疑似命令
Dx (以下の疑似命令の総称) を使ってデータを定義すると、現在のセクションに、値のバイナリが出力されます。
※疑似命令は、CPU の命令と同じような形で扱われますが、NASM 側で処理される命令です。
定義するデータのサイズごとに、命令の名前が異なります。
上記の命令の後に空白を記述した後、整数/浮動小数点数/文字列などの値を記述します。
値をカンマ (,) で区切ると、連続して複数個定義できます。
※浮動小数点数を指定する場合は、必ず '.' を含める必要があります。
リトルエンディアンで出力されるため、2 byte 以上のサイズの数値は、下位バイトから順に出力されます。
※疑似命令は、CPU の命令と同じような形で扱われますが、NASM 側で処理される命令です。
DB | BYTE (1byte) |
---|---|
DW | WORD (2byte) |
DD | DWORD (4byte) 整数または単精度浮動小数点数 |
DQ | QWORD (8byte) 整数または倍精度浮動小数点数 |
DT | TWORD (10byte:80bit) x87 の拡張倍精度浮動小数点数 |
DO | OWORD (16byte:128bit) |
DY | YWORD (32byte:256bit) |
DZ | ZWORD (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 と同じように、データのサイズごとに命令の名前が異なります。
命令の後に空白を開け、次に、確保するデータの数を、数値で指定します。
このデータ領域は、実行時に確保されるものであり、出力ファイルには、BSS セクション全体のサイズだけが記録されます。
実際にサイズ分のバイナリデータが出力されるわけではありません。
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 を以下の内容に置き換えて、バイナリコードを直接出力しても、実行ファイルは問題なく動作します。
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) を使って定義します。
最初の文字から順に、2 byte 整数として値を読み込み、それを 2 byte 整数として出力します。
結果的に、先頭の文字から順に、そのまま同じ順番で文字列が出力される形になりますが、最後に残った文字が、指定サイズに達しない場合、残りの文字は 0 として扱われます。
そのため、文字列全体を N バイト境界の文字列として定義したい場合は、DW や DD を使って、自動で余白の 0 のバイトを出力することができます。
この場合、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 の文字列を定義したい場合は、以下の特殊演算子を使用します。
() 内で UTF-8 の文字列を受け取り、その文字列を、UTF-16 または UTF-32 (2byte または 4byte の整数配列) に変換します。
le はリトルエンディアン、be はビッグエンディアンです。
指定がない場合、デフォルトは、リトルエンディアンです。
アセンブラのソースファイルが UTF-8 エンコーディングの場合、' " ` で囲まれた文字列は、そのまま UTF-8 文字列になります。
そのため、直接 UTF-8 文字列を記述して、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 命令に対して使うことも出来ます。
主にデータ定義で使いますが、CPU 命令に対して使うことも出来ます。
times 10 db 5 ; (1byte) 05 を 10個 定義 times 3 nop ; NOP 命令を3回
INCBIN
INCBIN でファイル名を指定すると、指定されたファイルの内容を読み込み、バイナリデータとしてそのまま出力します。
NASM の -i/-I オプションで指定された、インクルードファイル検索パスが有効になります。
ファイル名の後に、カンマで区切って、1〜2つの数値を指定すると、「先頭のスキップするバイト数」と「読み込む最大バイト数」を指定できます。
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つの数値を指定すると、「先頭のスキップするバイト数」と「読み込む最大バイト数」を指定できます。
境界合わせ
ALIGN と ALIGNB マクロを使うと、コードやデータの次の位置を、指定バイト数の境界に合わせることができます。
実際の処理としては、次の命令やデータの位置を、指定された境界位置に合わせるため、余分なバイトを出力するための命令に置き換えます。
例えば、4 byte の数値を指定アドレスから読み込みたい場合、そのアドレスが 4 の倍数になっていないと、パフォーマンスが落ちる場合があります。
また、アライメントが強制されている SSE などの命令を使って、メモリから読み書きしたい場合は、アドレスが特定のサイズの倍数になっている必要があります。
境界合わせは、自動で行われることはないので、必要に応じて、手動で行う必要があります。
実際の処理としては、次の命令やデータの位置を、指定された境界位置に合わせるため、余分なバイトを出力するための命令に置き換えます。
例えば、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 に置き換わることになります。
通常は、コードとデータのセクションでは ALIGN を使用し、BSS セクション (初期化されていないデータ) では ALIGNB を使用します。
データセクションで ALIGN を使用した場合、2番目の引数を省略すると、追加のバイトは 90h で埋められますが、何の値で埋めたとしても特に問題はないので、気にしないでください。
気になるのであれば、2番目の引数に db 0 を指定して、0 の値で埋めることもできます。
カンマで区切った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 の値で埋めることもできます。