home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

Assembler/ForFun(x86_32)/06, 32bit Windows with NASM

Assembler/ForFun(x86_32)/06, 32bit Windows with NASM

Assembler / ForFun(x86_32) / 06, 32bit Windows with NASM
id: 788 所有者: msakamoto-sf    作成日: 2010-09-19 14:52:01
カテゴリ: Assembler 

これまでDOSやBIOS上でのアセンブラプログラミングを体験してきました。
本記事ではWindows上での32bitアセンブラプログラミングを体験してみます。

アセンブラはNASMを使い、リンカはVisualC++2008 Express Edition SP1のlink.exeを使います。


"Hello, World!" printf()版

まずはC言語での定番、printf("Hello, World!") をアセンブラで書いてみましょう。
main(), printf()ともに呼び出し規約はcdeclなので、アセンブラレベルでは"_"を頭につけて、CRTライブラリとのリンク時に名前解決出来るようにしておきます。また "_main"はCRTライブラリ側からリンクされる為"global"を指定して公開します。"_printf"については本体がCRTライブラリにあるため、"extern"を指定して他のモジュールからインポートするシンボルであることを明示します。

参考:C言語系/呼び出し規約/x86/cdecl

hello1.asm:

global _main
extern _printf

section .text
_main:
    PUSH EBP
    MOV EBP, ESP

    PUSH msg
    CALL _printf

    MOV EAX, 2

    MOV ESP, EBP
    POP EBP
    RET

section .data

msg:
    db `Win32\r\nWorld!`, 0

コンパイル:

> nasm -fwin32 hello1.asm

ここで注意ですが、出力フォーマット"-f"オプションには"win32"を指定します。歴史的な理由で、"obj"を指定するとOMF(Borlandコンパイラなどが使うオブジェクトファイル形式で、COFFとは異なる)、また"coff"を指定するとDJGPPというDOS用に移植したgcc向けのフォーマットで出力されてしまいます。
紛らわしいですが、Visual C++ のリンカで扱えるCOFF形式で出力するには、"-fwin32"を指定します。

これでhello1.objが生成されます。リンクして実行してみます。"_main"があるので自動的にコンソールアプリケーションとしてリンクされます。msvcrt.libを明示的に指定します。

> link /OUT:hello1.exe hello1.obj msvcrt.lib
> hello1.exe
Win32
World!
> echo %ERRORLEVEL%
2

"_main"の戻り値(EAX)は、コマンドプロンプト上では"%ERRORLEVEL%"環境変数として取得出来ます。

なおリンクではマルチスレッド対応のDLLリンク形式である"msvcrt.lib"を使っています。静的リンクやデバッグバージョンを使う場合は他のCRTライブラリを指定します。VC++が提供しているCRTライブラリについては下記MSDNを参照して下さい。

  • "MSDN Library" > "Development Tools and Languages" > "Visual Studio 2010" > "Visual Studio" > "Visual Studio Languages" > "Visual C++" > "Visual C++ Reference" > "Visual C++ Libraries Reference" > "Run-Time Library" > "C Run-Time Libraries"

"Hello, World!" Win32 API - MessageBox()版

今度はGUIアプリケーションとして Win32API の MessageBox() 関数で"Hello, World!"を表示してみます。
MessageBox()にもASCII版のMessageBoxA()とUnicode版のMessageBoxW()がありますが、今回はASCII版を使います。
呼び出し規約はWINAPI、つまりstdcall呼び出し規約を使います。( C言語系/呼び出し規約/x86/stdcall )
WinMain()やMessageBoxA()のシンボル名は、アセンブラ中では"_"を接頭辞とし、"@" + スタック上に積まれた引数のバイト数 を接尾辞とします。WinMain()もMessageBoxA()も4バイトの引数を4つ取りますので、"@16"を後ろにつけます。

hello2.asm:

global _WinMain@16
extern _MessageBoxA@16

section .text
_WinMain@16:
    PUSH EBP
    MOV EBP, ESP

    PUSH 0
    PUSH caption
    PUSH msg
    PUSH 0
    CALL _MessageBoxA@16

    MOV EAX, 2

    MOV ESP, EBP
    POP EBP
    RET 16

section .data

caption:
    db "Hello, ", 0

msg:
    db `Win32\r\nWorld!`, 0

コンパイル:

> nasm -fwin32 hello2.asm

リンク:

> link /OUT:hello2.exe hello2.obj user32.lib msvcrt.lib

実行するとメッセージボックスが表示され、OKボタンで終了します。
なお、Win32GUIアプリケーションの場合に"ERRORLEVEL"で終了コードを取り出すには一工夫必要です。
というのは、単純にコマンドプロンプトからGUIアプリケーションを起動すると、コマンドプロンプト側はGUIアプリケーションの終了を待ちません。コマンドプロンプトでは続けて別のアプリケーションやコマンドを実行出来るようになっており、GUIアプリケーションの戻り値を受け取れません。
これを回避するには、コマンドプロンプトのstartコマンド + "/wait"オプション付きでGUIアプリケーションを実行します。起動されたGUIアプリケーションの終了を待ち、"ERRORLEVEL"で終了コードを取得することが出来ます。

> start /wait hello2.exe
(メッセージボックスのOKボタンをクリックするまで、コマンドプロンプトに戻らない)
> echo %ERRORLEVEL%
2

ERRORLEVELの詳細とGUIアプリでの取得方法については、下記リンクなどを参考にして下さい。

CRTを使わない "Hello, World!"

これまでのサンプルではいずれもVisual C++が提供するCRT(C RunTime library)である"msvcrt.lib"をリンクしてきました。今度はCRTを使わないWin32アセンブラプログラミングを体験してみましょう。

CRTを使わない場合、エントリポイントとなる関数を自分で用意する必要があります。通常のWinMain(), main()などはCRTが提供する真のエントリポイントが呼ばれた後、CRTが必要とする様々な前処理やC++ならグローバルオブジェクトのコンストラクタ処理などが呼ばれた後に実行されます。CRTを使わない場合、そうした処理も自前で実装する必要があります。本記事の場合は、サンプルとして極めて単純な処理しか行わないので、そうした下準備は不要となり、すぐにメイン処理に進むことが出来ます。

CRTを使う場合、エントリポイントのアドレスはリンカが自動的に決定してくれます。main()関数が定義してあればコンソールアプリケーションとしてリンクし、CRT中のmainCRTStartup()をエントリポイントに設定します。WinMain()関数が定義してあればGUIアプリケーションとしてリンクし、CRT中のWinMainCRTStartup()をエントリポイントに設定します。
CRTを使わない場合、自分で用意したエントリポイントを "/ENTRY" リンカオプションで指定します。また、コンソール/GUIアプリケーションの区別も、"/SUBSYSTEM" リンカオプションで指定します。コンソールアプリケーションの場合のエントリポイントはcdel規約、GUIアプリケーションの場合のエントリポイントはstdcall規約にします。

CRT中のエントリポイントと "/ENTRY" リンカオプションの詳細は下記MSDNを参照して下さい。

"Hello, World!" console without CRT

まずコンソール版をCRT無しにしてみます。CRTが無いとprintf()等が使えなくなりますので、Win32 APIだけでコンソールに出力してみます。
GetStdHandle(STD_OUTPUT_HANDLE)で標準出力のファイルハンドルを取得し、WriteFile()を使って文字列を書き込みます。両APIの詳細についてはMSDNを参照して下さい。

ソースコードは次のようになります。
hello3.asm:

global _MyEntry
extern _GetStdHandle@4
extern _WriteFile@20

section .text
_MyEntry:
    PUSH EBP
    MOV EBP, ESP

    SUB ESP, 8
    ; スタック上に確保したローカル変数を見やすくする為のマクロ
    %define hFile EBP - 4
    %define lpNumberOfBytesWritten EBP - 8

    ; HANDLE hFile = GetStdHandle(STD_OUTPUT_HANDLE);
    PUSH -11  ; STD_OUTPUT_HANDLE
    CALL _GetStdHandle@4
    MOV [hFile], EAX

    ; WriteFile() arguments
    ; __inout_opt  LPOVERLAPPED lpOverlapped
    PUSH 0
    ; __out_opt LPDWORD lpNumberOfBytesWritten
    LEA EAX, [lpNumberOfBytesWritten]
    PUSH EAX
    ; __in DWORD nNumberOfBytesToWrite
    MOV EAX, [msg_len]
    PUSH EAX
    ; __in LPCVOID lpBuffer
    PUSH msg
    ; __in HANDLE hFile
    MOV EAX, [hFile]
    PUSH EAX
    CALL _WriteFile@20

    MOV ESP, EBP
    POP EBP
    RET

section .data

msg:
    db `Win32\r\nWorld!`, 0
msg_len:
    dd $ - msg

コンパイル+リンク+実行:

> nasm -fwin32 hello3.asm
> link /OUT:hello3.exe /ENTRY:MyEntry /SUBSYSTEM:CONSOLE hello3.obj kernel32.lib
> hello3
Win32
World!
> echo %ERRORLEVEL%
1

WriteFile()が成功を返したEAX = 1がそのままプロセスの戻り値になっていることが確認出来ます。

"Hello, World!" win32-gui without CRT

続いてMessageBox()を使ったサンプルを、CRT無しに対応させてみます。
こちらは元々MessageBoxA()呼び出しだけなので、そのままWinMainをエントリポイントに指定出来ます。

hello2.asmそのままでは面白くないので、"WinMain"を"MyWinMain"にリネームし、戻り値も変えてみました。
hello4.asm:

global _MyWinMain@16
extern _MessageBoxA@16

section .text
_MyWinMain@16:
    PUSH EBP
    MOV EBP, ESP

    PUSH 0
    PUSH caption
    PUSH msg
    PUSH 0
    CALL _MessageBoxA@16

    MOV EAX, 4

    MOV ESP, EBP
    POP EBP
    RET 16

section .data

caption:
    db "Hello, ", 0

msg:
    db `Win32\r\nWorld!`, 0

コンパイル+リンク+実行:

> nasm -fwin32 hello4.asm
> link /OUT:hello4.exe /ENTRY:MyWinMain /SUBSYSTEM:WINDOWS hello4.obj user32.lib
> start /wait hello4.exe
(メッセージボックスのOKボタンをクリックするまで、コマンドプロンプトに戻らない)
> echo %ERRORLEVEL%
4

まとめ

NASMとVC++の提供するリンカを使って、Win32アセンブラプログラミングを体験しました。

今回はVC++のリンカを使いましたが、フリーのWin32用リンカとして"ALink"や"GoLink"などもあります。また"GoLink"開発元では、アセンブラ/リソースコンパイラ/デバッガも開発されています。

NASMでのWin32アセンブラプログラミングはWeb上にも資料が豊富に存在し、お手軽・お気軽に始められる環境だと言えます。
但し記事によってはNASM以外のツールチェインが異なる場合があり、GoLinkを使っていたりMSYS/MinGW環境だったりしますので、そこだけ注意が必要でしょう。



プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2010-09-19 20:58:42
md5:611f763c591c5d7dee2f77f3e4b7a025
sha1:aafc7b3a43c74dc909e2c0aec614b6ec46c7688e
コメント
コメントを投稿するにはログインして下さい。