Top PageMASMのメモ書き TOP

モデルと、スタートアップ

.model

書式


.model memorymodel (,langtype) (,stackoption)

説明

当該アセンブリ言語ファイルをアセンブルする時のモデルを設定する。
通常、CPU指定(例: .586 )の次に記述する。

パラメータ

memorymodel
メモリーモデルを示す。
多くのMS-DOS用のC言語コンパイラにおけるメモリモデルと同一であり、 それらにてコンパイルされた*.objファイルとリンクする為に指定する。
尚、32bit版のWindows用アプリケーションのメモリモデルは"FLAT"であり、 Visual C++等でコンパイルされた*.objファイルとリンクする場合は、"FLAT"を指定する。
nearは、その領域が1つのセグメント空間にある事を示す。
code & data combinedがYesの場合は、コード領域とデータ領域が同じセグメント空間にあることを示す。

memorymodelsegmentcodedata code & data combinedOperatin Systemsegment group
TINY 16bit near near yes (MS-DOS *.com)_TEXT,_DATA,STACK,_BSS
SMALL 16bit near near no (MS-DOS *.exe)_DATA,STACK,_BSS
COMPACT 16bit near far no (MS-DOS *.exe)_DATA,STACK,_BSS
MEDIUM 16bit far near no (MS-DOS *.exe)_DATA,STACK,_BSS
LARGE 16bit far far no (MS-DOS *.exe)_DATA,STACK,_BSS
HUGE 16bit far far no (MS-DOS *.exe)_DATA,STACK,_BSS
FLAT 32bit near near yes (Windows *.exe)_TEXT,_DATA,STACK,_BSS
※FLATモデル以外のセグメントグループ名は"DGROUP"となり、FLATモデルでは"FLAT"となる。
※尚、stackoptionにて"FARSTACK"とした場合、DGROUPからSTACKは外される。

langtype
デフォルトの関数の言語タイプを設定する。
関数規約の詳細は、別途「MASM, VC++ における関数規約」を参照。

langtypeVisual C++における関数規約
C __cdecl
SYSCALL __syscall (廃止)
STDCALL __stdcall
BASIC
FORTRAN __fortran (廃止)
PASCAL __pascal (廃止)
※BASIC, FORTRAN, PASCALは、全く同じ規約です。
※又、PASCALは、WINAPIを呼ぶ時の規約と同一です。

stackoption
スタック領域の場所を示す。

stackoption スタック領域の場所
NEARSTACK SS=DS
FARSTACK SS≠DS

.startup

書式


.startup

説明

MS-DOS用の、スタートアップのコードを生成する。
modelの指定内容によって、生成コードが異なる。

memorymodel=TINYの場合。


	org	0100h
@Startup:

memorymodel=TINY,FLAT以外、stackoption=NEARSTACK(SS=DS)の場合
SS==DS(DGROUP)として、SPレジスタを調整するコードを生成する。


@Startup:
	mov	ax, DGROUP
	mov	ds, ax
	mov	bx, ss
	sub	bx, ax
	shl	bx, 1		; If .186 or higher, this is
	shl	bx, 1		;   shortened to shl bx, 4
	shl	bx, 1
	shl	bx, 1
	cli			; Not necessary in .286 or higher
	mov	ss, ax
	add	sp, bx
	sti			; Not necessary in .286 or higher

;	statements

	END	@Startup

memorymodel=TINY,FLAT以外、stackoption=FARSTACK (SS≠DS)の場合


@Startup:
	mov	dx, DGROUP
	mov	ds, dx

;	statements

	END	@Startup

.exit

書式


.exit	expression

説明

MS-DOS用の、シェル(command.com等)に戻るコードを生成する。


	mov	ah, 04Ch
	int	21h

パラメータ

expression
シェールに返すコードを0〜255で指定する。
主に、エラーコードとして用いられ、指定した場合は以下のコードを生成する。


	mov	ax, 04C00h + expression
	int	21h

プロシージャ

PROTO

書式


label PROTO (distance) (langtype) ([,parameter:tag]...)

説明

関数のプロトタイプ宣言及び外部宣言をする事ができる。
INVOKEディレクティブで関数を呼ぶとき、それまでの行に関数が定義されなかった場合はエラーとなる。
PROTOにて、プロトタイプ宣言(若しくは外部宣言)する事で、アセンブラにこの関数がある事を通知できる。

パラメータ

label
関数名を指定する。

distance
関数の場所を指定する。

distance
near メモリモデルが、TINY, SMALL, COMPACT, FLATの時に指定可。
far メモリモデルが、MEDIUM, LARGE, HUGEの時に指定可。
near16 80386以降のCPUで、16bit, 32bitモード混在のアプリケーションを書く時に指定する。
far16
near32
far32

langtype
関数の呼出規約を指定する。

langtype規約(Visual C++)
C __cdecl
SYSCALL __syscall
STDCALL __stdcall
BASIC
FORTRAN __fortran
PASCAL __pascal

parameter:tag
当該関数の引数とその型を指定する。
parameterは引数の名前、tagは引数の型を示す。
配列変数は不可。配列を渡す時は、ポインターで渡す事。

tag 内容
BYTE 1BYTEの符号無し整数 (0〜255)
SBYTE 1BYTEの符号付き整数 (-128〜127)
WORD 2BYTEの符号無し整数 (0〜65,535)
SWORD 2BYTEの符号付き整数 (-32,768〜32,767)
DWORD 4BYTEの符号無し整数 (0 〜 4,294,967,295)
SDWORD 4BYTEの符号付き整数 (2,147,483,648 〜 +2,147,483,647)
FWORD 6BYTEの整数
QWORD 8BYTEの整数
TBYTE 10BYTEの整数
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"があり、 初代ペンティアムでは1clockのペナルティーが発生する。
又、連続して関数を呼ぶときに、連続した関数を全て終えてからではなく、 各関数毎にスタックポインタを元に戻すので、その文"add sp,+n"命令が増える事となる。
(これは、スタックの破棄を関数側で行う規約(__stdcall等)を使えば、"ret n"命令で戻ってくる為、関係ない。)

MASM, VC++ における関数規約

引数と名前修飾

Visual C++ MASM langtype 引数 stack破棄 名前修飾
__cdecl C stack (右からpush) 呼出側 _Label
__stdcall STDCALL stack (右からpush) 関数側 _Lebel@n
__fastcall ecx, edx
stack (右からpush)
関数側 @Label@n
__thicall
(引数固定長)
stack (右からpush)
ecx (this)
関数側 _Label@n
__thicall
(引数可変長)
stack (右からpush)
thisは最後にpush
関数側 _Label
__syscall SYSCALL stack (右からpush) 呼出側 Label
__fortran FORTRAN stack (左からpush) 関数側 Label
__pascal PASCAL stack (左からpush) 関数側 Label
BASIC stack (左からpush) 関数側 Label
※名前修飾における関数名の最後の@nについて、nは引数の合計バイト数を10進数で記述する。
 尚、引数のサイズは実際のサイズであり、例えば32bitモードの時のcharは、4Byte(32bit)の幅を持つ。
※Visual C++においては、extern "C" を宣言しない限り、独自の名前修飾が行われる。
※MASMには、__fastcall,__thiscallに該当する言語タイプは無いため、別の規約で書く必要がある。
 例えば、__fastcallは、SYSCALLを使い、関数名の先頭に"@"、後ろに"@"と引数のサイズを10進数で書き、  ret命令のパラメータには、スタックに詰まれた引数の数を指定して関数から戻るように記述する。

Visual C/C++の戻り値について

8,16,32[bit]の実値(char,int等) eax
64[bit]の実値 edx:eax
ポインタ・参照 eax

Visual C/C++におけるレジスタについて

呼び出された関数は、ebp, ebx, edi, esi レジスタを保持しなければならない。
つまり、eax, ecx, edxは破壊して良い。

Visual C/C++における最適化

以下の条件時について、以下の条件を満たす場合、

  1. リンク時のコード生成を有効(/LTCG)をした場合、且つ、
  2. ある関数において、その関数のある"*.obj"ファイル及び、その関数を呼ぶコードがある"*.obj"ファイルについて、コンパイル時に、リンク時のコード生成を有効(/GT)をした場合

最適化の為、関数規約を無視し、関数の入出力について以下の最適化を行う。

  • レジスターに引数を渡す(__fastcallとか関係無しに)。
  • 配置の為に、引数の順番を並べ替える。
  • 未使用の引数を削除する。

但し、一つでも、リンク時のコード生成が有効(/GT)でない"*.obj"ファイルがあった場合は、関数呼出規約が守られる。
例えば、アセンブリ言語で書かれたソースからアセンブルされた"*.obj"ファイル等。

つまり、アセンブリ言語で書くよりも、コンパイラとリンカに最適化を任せてしまった方が、 多くの場合は、アセンブリ言語で関数を記述するよりも実行速度が早くなる。

Visual C++における名前修飾について

Visual C++の場合、独自の名前修飾が行われる。
修飾された名前には、

  • クラス名
  • メンバー名
  • 戻り値の型
  • 引数の型

等の情報が含まれている。

従って、従来の名前修飾がされている関数(アセンブリ言語で書かれた等) をC++言語から呼ぶ場合は、extern宣言に"C"を追加する必要がある。
例)


extern "C" void func();

また、C++言語で書かれた関数を、C言語、アセンブリ言語から呼ぶ場合は、 従来の名前修飾修飾にする為、関数にextern "C"と宣言する。
例)


extern "C" __cdecl void func(){
  //statement;
}