ウィンドウをきっかけに Windows の内部の仕組みを探る (前半の補足)アセンブラでウィンドウを生成する方法
前回の記事では以下の事を学んだ。
- Windowsにおいてウィンドウを表示する機能は,Windows API という API によって提供されている。
- Windows API の中で,特に user32.dll というファイルがウィンドウ処理を担当している。
(そして user32.dll の中味を逆アセンブルで解析するのはやめておこう,という事になった。)
ここではその記事の補足として,実際にアセンブリ言語のプログラムから user32.dll を呼び出し,ウィンドウを表示させてみよう。
ダイアログボックスやウィンドウの表示を
- インラインアセンブラ
- NASM
- MASM
の3つの方法で試してみる。使い方を詳しく書くので,アセンブラ初心者でも大丈夫。
(※他のサイトでは,よく「このソースをアセンブルして実行するとこうなります」のように書いてあるが,肝心のアセンブルの実行コマンドが書かれていなかったりする。これはやや不親切。)
(1)インラインアセンブラの方法
インラインアセンブラとは…
Visual C++ に付属のコンパイラでは,C言語のコード中に,機械語のソースコードを埋め込むことができる。
通常のCのソース中に
__asm{ 〜〜(ここにアセンブラを書く) }
のように記述する。
参考:VC++でインラインアセンブラ
http://codezine.jp/article/detail/393?p=1
下記はインラインアセンブラのサンプル。(movtest.c)
asmブロック中で mov 命令を使い,i, j という変数にそれぞれ 1, 2 を代入している。
#include <stdio.h> int main() { int i, j; __asm { mov i, 1 mov j, 2 } printf( "i = %d, j = %d \n", i, j ); return 0; }
コンパイルのために特別な事は何も必要ない。
cl movtest.c
これで movtest.exe ができて,コマンドラインで実行すると i = 1, j = 2 が表示される。
では,インラインアセンブラでダイアログボックスを表示させてみよう。(w1.c)
#include <windows.h> int main(int argc, char* argv[]) { HMODULE libhandle = LoadLibrary("user32.dll"); typedef int (__stdcall *FUNCTION)(void); FUNCTION _proc = (FUNCTION)GetProcAddress(libhandle, "MessageBoxA"); unsigned long _arg1, _arg2, _arg3, _arg4 ; unsigned long _ret; // 引数を設定 _arg1 = (unsigned long)0; _arg2 = (unsigned long)"本文"; _arg3 = (unsigned long)"タイトル"; _arg4 = (unsigned long)0; // スタックに引数を追加(スタックなので逆順) // 呼び出し _asm { mov eax, _arg4 push eax mov eax, _arg3 push eax mov eax, _arg2 push eax mov eax, _arg1 push eax call _proc mov _ret, eax } FreeLibrary(libhandle); // 返り値 printf("_ret = %d\n", _ret); return 0; }
call命令で user32.dll から MessageBoxA 関数を呼び出している。
cl w1.c でコンパイル。実行するとボックスが出る。
(2)NASMを使う方法
NASM(Netwide Assembler)は親しみやすいアセンブラで,NASKのような互換言語を生んだ。
このアセンブリ言語を使ってダイアログボックスを表示させるにあたって,下記のサイトに従って進めてみる。
アセンブリ言語
http://maccyo.hp.infoseek.co.jp/assembler/assembly.html#003_1
まずNASMをダウンロード。
http://www.nasm.us/pub/nasm/releasebuilds/2.05.01/
nasm-2.05.01-win32.zipを取得。
次に,リンカ(ALINK.EXE)をダウンロード。
http://alink.sourceforge.net/download.html
- win32.lib : Win32 Import library (win32.lib)
- alink.exe : ALINK (Win32 version)
をそれぞれ取得。
nasm.exeと同じフォルダ上で,下記のコードを w2.asm として保存。
bits 32 extern MessageBoxA section .text global my_entry my_entry: push dword 0 push dword title push dword string push dword 0 call MessageBoxA ret section .data title: db 'タイトル',0 string: db '本文',0
my_entryと書いてある部分がエントリポイント(プログラム実行開始地点)になっている。
これをアセンブルする。(コンパイル言語Cの場合と異なり,コンパイルするとは言わない。)
nasm w2.asm -fwin32
すると,objファイルができる。これは生のマシン語ファイルだ。
次に,このオブジェクトファイルを外部ライブラリとリンクして,実行可能にする。
alink.exeと同じフォルダに w2.asm と WIN32.LIB を移動して,コマンドプロンプトから
alink w2.obj WIN32.LIB -oPE -entry my_entry
と打ち込む。
w2.exeが出力され,実行すると,先ほどと同様のメッセージボックスが表示される。
先ほどと違う点は,このプログラムはGUIだがアセンブラだけでコーディングして作った,ということ。
少し感慨深いのでは。
(3)MASMを使う方法
前項では,確かにアセンブラのみでコーディングした。
しかし途中でWIN32.LIBというライブラリファイルが出てきた。
ここではあくまで user32.dll の機能を確かめたいのだが,余計なライブラリファイルを介さずにプログラムを作り上げることは可能だろうか。
MASMを使う事で可能になる。
MASM(Macro Assembler)は,マイクロソフトがMS-DOS用に開発したアセンブラで,NASMよりもやや高機能。
NASMとの完全な互換性はない。
もし Visual Studio 2005 を利用しているのなら,下記サイトからMASM8.0を入手できる。
Microsoft Macro Assembler 8.0 (MASM) Package (x86)
http://www.microsoft.com/downloads/details.aspx?familyid=7A1C9DA0-0510-44A2-B042-7EF370530C64&displaylang=en
Visual Studio 2008 がインストールされている場合は,かわりに下記サイトからMASM32というフリーソフトを落とす。
MASM32
http://www.masm32.com/
downloadページから適当なミラーサイトを選ぶ。インストールには多少時間がかかる。
MASMの入門サイトとしては,下記の情報源がすばらしく役に立つ。
Win32 MASM プログラミング入門
http://www7.plala.or.jp/keny01/asm/win32/
Iczelion's Win32 Assembly Tutorials の翻訳
http://www.interq.or.jp/chubu/r6/masm32/masm006.html
(CPUの .386 という記述は適宜 .586 に読み替える。)
MASMの中核になるのは ml.exe である。
ml.exe と同じフォルダ(masm32\bin)に下記のコードを保存。(w3.asm)
.586 .model flat, stdcall NULL EQU 0 MessageBoxA proto :dword, :dword, :dword, :dword ExitProcess proto :dword .data TITLE1 DB 'タイトル', 0 MESSAGE DB '本文', 0 .code WinMainCRTStartup proc invoke MessageBoxA, NULL, offset MESSAGE, offset TITLE1, 0 invoke ExitProcess, 0 ret WinMainCRTStartup endp end
NASMの構文よりも多少複雑で,invokeなどの命令が使われている。
このソースコードを ml.exe でアセンブルする。使い方は
ml /coff /c /Cx w3.asm
とする。
w3.objが出来上がるが,Visual C++に付属のリンカ link.exe を使ってリンクしよう。
link /SUBSYSTEM:WINDOWS w3.obj kernel32.lib user32.lib
w3.exeができる。
これで,user32.dll と直接結びついた形でメッセージボックスを表示させる事ができた。
ml.exe の使い方は,下記サイトが参考になった。
MASMでexe作成
http://oshiete1.goo.ne.jp/qa4375462.htmlTutorial 2: MessageBox
http://www.interq.or.jp/chubu/r6/masm32/tute/tute002_Jp.html
ところで,ここまでメッセージボックスばっかりだったので,ウィンドウを作ってみよう。
コードは少し長くなる。
http://www.interq.or.jp/chubu/r6/masm32/tute/tute003_Jp.html の内容を少し変えた物をコメント付きで下記に掲載する。(w4.asm)
.586 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib ; Windows API を呼び出し include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib WinMain proto :DWORD,:DWORD,:DWORD,:DWORD .DATA ; 初期化済みデータ ClassName db "SimpleWinClass",0 ; ウィンドウクラス名 AppName db "ウィンドウのタイトル",0 ; ウィンドウ名 .DATA? ; 初期化しないデータ hInstance HINSTANCE ? ; インスタンスハンドル CommandLine LPSTR ? .CODE ; コード開始 start: invoke GetModuleHandle, NULL ; インスタンスハンドル取得 mov hInstance,eax invoke GetCommandLine ; コマンドライン取得 mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ; 下で定義したメイン関数へ invoke ExitProcess, eax ; 終了コード付きで終了 ; メイン関数 WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX ; ウィンドウ情報を設定 mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc ; ウィンドウを登録 invoke CreateWindowEx,NULL,\ ADDR ClassName,\ ADDR AppName,\ WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ NULL,\ NULL,\ hInst,\ NULL mov hwnd,eax invoke ShowWindow, hwnd,CmdShow ; 表示 invoke UpdateWindow, hwnd ; 表示領域を更新 .WHILE TRUE ; メッセージループ invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ; 終了コード ret WinMain endp ; ウィンドウプロシージャ WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY ; 閉じるメッセージの場合 invoke PostQuitMessage,NULL ; 破棄へ .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; デフォルト処理 ret .ENDIF xor eax,eax ret WndProc endp end start
前記事でも解説した CreateWindowEx 関数がここに出てきた。
プログラムの構造としては,C言語のウィンドウプロシージャのコードをそのままアセンブラに移植したような感じだ。(実際には逆だが)
先ほどと同じ手順でアセンブル&リンクする。
D:\masm32\bin>ml /coff /c /Cx w4.asm Microsoft (R) Macro Assembler Version 6.14.8444 Copyright (C) Microsoft Corp 1981-1997. All rights reserved. Assembling: w4.asm D:\masm32\bin>Link /SUBSYSTEM:WINDOWS w4.obj user32.lib Microsoft (R) Incremental Linker Version 5.12.8078 Copyright (C) Microsoft Corp 1992-1998. All rights reserved. D:\masm32\bin>w4.exe
アセンブラだけで作ったウィンドウが表示された。
まとめ
- VC++のインラインアセンブラ
- NASM
- MASM
の3つの手段で,アセンブリ言語により user32.dll の機能を呼び出し,ダイアログボックスやウィンドウを表示させた。
通常,ウィンドウを生成させる際には .NET / MFC / Windows SDK (windows.h) などなどあらゆるラッパーを使う。
しかし,実はそんなもの使わなくても,コアになるDLLさえあればよいのだ,という事がわかる。
今回の内容は,「ウィンドウをきっかけに Windows の内部の仕組みを探る (前半)フレームワークからDLLまでのスケール」の補足記事であり,後半の記事にスムーズにつなげるためのものだった。
シリーズのテーマは,「ウィンドウを生成するための機械語は?」「ウィンドウの正体は?」というもの。
今回は「機械語によって user32.dll を呼び出せる」という事が実際に体験できたので,次は,user32.dll そしてOSが具体的にどのような処理を行なっているのかを考える。
後半記事へ続く。
関連する記事:
メモリの中身を読んでみよう (プロセスをダンプ+解析する方法) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20081019/1224341559
自作のC言語プログラムから,BIOS設定(CMOS)を読み書きする方法 (の調査ログ) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20120104/p1
メモリ・CPUなどハードウェアの構成情報を,バッチで取得しよう (WSH/JScriptでWMIを使う方法) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20100906/p1