Microsoft Macro Assembler(以降、MASMと略)Version 6以降でアプリケーションを開発する際のメモ書きです。
この記事では、以下の内容について記します。
PROTO
書式
label PROTO (distance) (langtype) ([,parameter:tag]...)
説明
関数のプロトタイプ宣言及び外部宣言をする事ができる。
INVOKEディレクティブで関数を呼ぶ際、PROTO
ディレクティブにより前もってプロトタイプ宣言(若しくは外部宣言)する事により、アセンブラに関数がある事を通知できる。
(INVOKEディレクティブの前に関数、又はプロトタイプ宣言が無い場合は、エラーとなる。)
パラメータ
label
関数名を指定する。
distance
関数の場所を指定する。
distance | 説明 |
---|---|
near | 同一セグメントから呼び出される関数。 |
far | 他のセグメントから呼び出される関数。 |
near16 | 同一セグメントから呼び出される16bitコードの関数。16bit, 32bitモードが混在するプロジェクトにて、使用可。 |
far16 | 他のセグメントから呼び出される16bitコードの関数。16bit, 32bitモードが混在するプロジェクトにて、使用可。 |
near32 | 同一セグメントから呼び出される32bitコードの関数。同一セグメントから呼び出される関数。16bit, 32bitモードが混在するプロジェクトにて、使用可。 |
far32 | 他のセグメントから呼び出される32bitコードの関数。16bit, 32bitモードが混在するプロジェクトにて、使用可。 |
langtype
関数呼出規約を指定する。
省略した場合は、.modelディレクティブにて指定したlangtypeとみなされる。
関数呼出規約についての詳細は、MASM, Visual C++における関数呼出規約を参照。
parameter:tag
当該関数の引数とその型を指定する。
parameterは引数の名前、tagは引数の型を示す。
配列変数は不可。配列を渡す時は、ポインターで渡す事。
tag | 内容 |
---|---|
BYTE | 1 Byte の符号なし整数 (0~255) |
SBYTE | 1 Byte の符号付き整数 (-128~127) |
WORD | 2 Bytesの符号なし整数 (0~65,535) |
SWORD | 2 Bytesの符号付き整数 (-32,768~327,67) |
DWORD | 4 Bytesの符号なし整数 (0 ~ 4,294,967,295) |
SDWORD | 4 Bytesの符号付き整数 (2,147,483,648 ~ +2,147,483,647) |
FWORD | 6 Bytesの整数 |
QWORD | 8 Bytesの符号なし整数 |
SQWORD | 8 Bytesの符号付き整数 |
TBYTE | 10 Bytesの整数 |
REAL4 | 単精度 (4 Bytes) の浮動小数点数 |
REAL8 | 倍精度 (8 Bytes) の浮動小数点数 |
REAL10 | 10 Bytes の浮動小数点数 |
XMMWORD | Visual C++ における__m128 型を示す。 |
YMMWORD | Visual C++ における__m256 型を示す。 |
struc |
struc ディレクティブにて定義された構造体(※構造体のメンバー全てがスタックにコピーされ渡される)。 |
PTR tag |
tag へのポインタ(構造体へのポインタも可) |
NEAR PTR tag |
tag へのnearポインタ(構造体へのポインタも可) |
FAR PTR tag |
tag へのfarポインタ(構造体へのポインタも可) |
VARREG | 可変長引数 (記述する時は、引数の一番最後に記述する。) |
PROC ~ ENDP
書式
label PROC (distance) (langtype) (visibility) (USES reglist) ([,parameter:tag]...)
(LOCAL variable([count]):tag)
(statements)
label ENDP
説明
プロシージャー(関数)ブロックの開始(PROC
)と終了(ENDP
)を宣言する。
このプロシージャーは、CALL
命令の他、INVOKEディレクティブから呼び出すことが出来る。
パラメータ
label
プロシージャー名(関数名)を指定する。
distance
関数の場所を指定する。
PROTOディレクティブの項を参照。
langtype
関数の呼出規約を指定する。
PROTOディレクティブの項を参照。
visibility
関数の属性を指定する。
visibility | 属性 |
---|---|
PRIVATE | 外部から関数を隠す |
PUBLIC | 外部から関数が呼ばれる |
EXPORT |
USES reglist
プロシージャー(関数)が呼び出されたときに、最初にスタックに退避するレジスタを指定する。
parameter:tag
当該プロシージャー(関数)の引数と、その型を指定する。
parameterは引数の名前、tagは引数の型を示す。
配列を渡す時は、ポインタで渡す(配列の実体を渡す事は不可)。
引数がある場合、BP
, EBP
レジスタを自動で退避し、スタックフレームとして使用する。
PROTOディレクティブの項を参照。
LOCAL variable([count]):tag
関数内のみで使う変数を指定する。
variableは変数の名前、[count]は要素数がcountの配列変数である事を示し、tagは引数の型を示す(構造体も可)。
この変数はスタック領域に確保され、関数から戻る時に自動で破棄される。
ローカル変数がある場合、BP
, EBP
レジスタを自動で退避し、スタックフレームとして使用する。
スタックのサイズが許す限り、いくらでも定義できる。
statements
ここにコードを記述する。
例
以下の関数を記述した場合、
.186
.model small,stdcall
.dosseg
.code
func proc far uses ax ds di,
para1:word,
para2:far ptr WORD
local var[2]:word
lds di, para2
mov ax, [di]
mov var[sizeof(word)*0], ax
mov ax, para1
mov var[sizeof(word)*1], ax
ret
func endp
END
MASM 5.0以前のアセンブリ言語で記述すると、以下と同等の意味を持つ。
.186
_TEXT segment word public 'CODE'
_TEXT ends
_DATA segment word public 'DATA'
_DATA ends
STACK segment para stack 'STACK'
STACK ends
_BSS segment word public 'BSS'
_BSS ends
DGROUP group _DATA,STACK,_BSS
public func@6
_TEXT segment word public 'CODE'
assume cs:_TEXT, ds:DGROUP
_func@6:
push bp ;
mov bp, sp ;スタックフレームの作成
add sp, 0FFFCh ;ローカル変数領域の作成
push ax
push ds
push di ;USESで指定したレジスタの保存
lds di, [bp+8] ;ds:di ← 引数"para2"
mov ax, [di]
mov [bp-4], ax ;var[0] ← ax
mov ax, [bp+6] ;ax ← 引数"para1"
mov [bp-2], ax ;var[1] ← ax
pop di
pop ds
pop ax ;USESで指定したレジスタの復帰
leave ;ローカル変数の破棄
retf 6 ;__stdcallなので、引数を破棄して関数から戻る。
END
尚、上のコードを見て解るとおり、最適化はされないので、 パイプラインの効率を気にする、且つ、どうしてもアセンブリ言語で書きたいのであれば、 PROC
~ ENDP
を使わず、自力で最適化して関数を書く方が良い。
又、C言語から呼ぶ場合は、__fastcall
を使う手もある。例えば、Visual C++にて
extern "C" __fastcall void func(int para1, int para2, int para3);
上記の様に宣言された関数は、アセンブリ言語にて
(extern "C"が無いと、Visual C++独自の名前修飾が行われてしまう。)
.386
.model flat,c
.code
@func@12 proc near syscall,
para3:SDWORD ;この場合、スタックフレームがebpに作成される。
; ecx = para1:SDWORD
; edx = para2:SDWORD
; statement
ret 4 ;スタックに入った分の引数は、破棄する。
@func@12 endp
END
または、
.386
.model flat,c
.code
@func@12 proc near syscall
; ecx = para1:SDWORD
; edx = para2:SDWORD
; [esp+4] = para3:SDWORD ;何かをpushした時は、オフセットに気をつける。
; statement
ret 4 ;スタックに入った分の引数は、破棄する。
@func@12 endp
END
と記述する事で、呼び出すことができる。
尚、エル・エス・アイ・ジャパン社のLSI-C86(Wonder Witch版を除く)における関数規約では、引数はレジスタで渡される。
従って、__cdecl
等、MASMで対応しているlangtypeでは記述不可能なので、自力でLSI-C86の関数規約に基づいた関数を書く必要がある。
INVOKE
書式
INVOKE expression ([, arguments]...)
説明
指定された関数規約に基づき、関数expressionを呼ぶ。
パラメータ
expression
関数名を指定する。
尚、関数規約に基づく名前修飾は自動で行われる。
arguments
関数の引数を指定する。
引数には、実数、レジスタ、レジスタのペア(dx::ax
等)、アドレス式(ADDR
で始まる式)が指定可能。
例
func PROTO near c, var:far ptr word
と宣言された関数を呼ぶ
invoke func,addr label[si]
は、
push DGROUP ;ローカル変数の場合、"push ss"となる。
lea ax,[label + si] ;ローカル変数の場合、bpレジスタ経由となるため、
push ax ;配列要素はdiかsiレジスタのみが可となる。
call _func
add sp,+4
と変換される。
これにより、他言語で記述したプログラムを簡易的に呼び出すことが出来る。
(※LSI-C86は、引数はレジスタで渡される規約の為、__cdecl
等の規約での呼出は不可。)
尚、最適化はしないので、パイプラインの効率を気にするのであれば、invoke
を使わず、自力で関数規約に基づいたコードを書く方がよい。
例えば上記のコードの場合、lea ax,[n]
の後にpush ax
があり、 初代ペンティアムでは1 clockのペナルティーが発生する。
又、連続して関数を呼ぶときに、連続した関数を全て終えてからではなく、 各関数毎にスタックポインタを元に戻すので、その文add sp,+n
命令が増える事となる。
(これは、スタックの破棄を関数側で行う規約(__stdcall
等)を使えば、ret n
命令で戻ってくる為、関係ない。)