3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

MASMにおける関数の記述方法(C/C++とのクロス開発を踏まえた)

Last updated at Posted at 2020-06-28

Microsoft Macro Assembler(以降、MASMと略)Version 6以降でアプリケーションを開発する際のメモ書きです。
この記事では、以下の内容について記します。

  1. MASMにおける、関数のプロトタイプ宣言(PROTO)
  2. MASMにおける、関数の記述(PROC ~ ENDP)
  3. MASMにおける、関数の呼び出し(INVOKE)

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

尚、上のコードを見て解るとおり、最適化はされないので、 パイプラインの効率を気にする、且つ、どうしてもアセンブリ言語で書きたいのであれば、 PROCENDPを使わず、自力で最適化して関数を書く方が良い。
又、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命令で戻ってくる為、関係ない。)

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?