Ściągnij przykładowy plik stąd.
.386
.model flat, stdcall
.data
.code
start:
end start
Wykonanie zaczyna się od pierwszej instrukcji poniżej etykiety o nazwie określonej po słowie kluczowym end. W powyższym szablonie, wykonanie rozpocznie się od pierwszej instrukcji poniżej etykiety start . Wykonywanie będzie następowało instrukcja po instrukcji aż wystąpią instrukcje sterujące, takie jak jmp, jne, je, retitp. Te instrukcje zmieniają kolejność wykonywania programu przekazując sterowanie do innych instrukcji. Kiedy program potrzebuje wyjść do Windows, powinna być wywołana funkcja API: ExitProcess.
ExitProcess proto uExitCode:DWORD
Powyższa linia jest nazywana prototypem funkcji. Prototyp funkcji definiuje atrybuty funkcji dla linkera w celu umożliwienia wykonania sprawdzenia typów. Format prototypu funkcji jest następujący:
FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,...
W skrócie, nazwa funkcji następująca po słowie PROTO a następnie lista parametrów typów danych oddzielonych przecinkami. W przykładzie powyższym ExitProcess jest definiowane jako funkcja, która potrzebuje tylko jednego parametru typu DWORD. Prototypy funkcji są bardzo pożyteczne, kiedy używasz składni dalekiego wywołania: invoke. Możesz uważać invoke jako przykład wywołania ze sprawdzaniem typów. Na przykład, jeżeli wykonasz:
call ExitProcess
bez położenia podwójnego słowa (dword) na stos, assembler/linker nie będzie mógł wyłapać tego błędu dla ciebie. Zauważysz to później kiedy program się "wysypie". Ale jeżeli użyjesz :
invoke ExitProcess
linker poinformuje cię że zapomniałeś odłożyć na stos słowa i w ten sposób unikniesz błędu. Polecam używać invoke zamiast po prostu call. Składnia polecenia invoke jest następująca:
INVOKE wyrażenie [,arguments]
wyrażenie może być nazwą funkcji lub wskaźnikiem do funkcji. Parametry funkcji są oddzielone przecinkami.
Większość prototypów
funkcji dla funkcji API są przechowywane w plikach include. Jeżeli używasz
MASM32 (Hutch'a), będą one w folderze MASM32/include. Pliki include mają rozszerzenie .inc
i prototypy funkcji dla funkcji DLL są zapisane w pliku .inc z taką samą nazwą jak DLL.
Na przykład ExitProcess jest eksportowana przez kernel32.lib więc prototyp funkcji dla
ExitProcess jest zapisany
w kernel32.inc.
Możesz również
tworzyć prototypy funkcji dla własnych funkcji.
W moich przykładach
użyję pliku windows.inc (Hutch'a), który możesz ściągnąć z http://win32asm.cjb.net
Wracając do ExitProcess, parametr uExitCode jest wartością, którą chcesz, aby program zwrócił do Windows po zatrzymaniu programu. Możesz wywołąć ExitProcess nastąpująco:
invoke ExitProcess, 0
Wpisz tą linię dokładnie poniżej etykiety start, uzyskasz program win32, który natychmiast wyjdzie do Windows, niemniej będzie to program działający.
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:
invoke
ExitProcess,0
end
start
opcja casemap:none mówi MASM aby rozróżniał wielkość liter więc ExitProcess
i exitprocess różnią się. Zauwasz
nową dyrektywę, include.
Ta dyrektywa zawiera nazwę pliku, który chcesz dołączyć w miejsce gzie występuje
dyrektywa. W powyższym przykładzie, kiedy MASM odczytuje linię include
\masm32\include\windows.inc, zostanie otwarty plik windows.inc
,który znajduje się w folderze \MASM32\include i wykona zawartość tego pliku jak gdybyś
go tam wkleił. Plik windows.inc Hutch'a zawiera definicje i struktury programu win32. Nie zawiera
żadnych prototypów funkcji. Zawartość pliku windows.inc jest zrozumiała. Hutch
i ja staramy się dopisać tak wiele stałych i struktur jak to możliwe, ale jest jeszcze
dużo do dołączenia. Będę go stale uaktualniał. Sprawdzaj nasze strony domowe
w celach uaktualnień.
Właśnie zpliku windows.inc, twój program
pobiera definicje stałych i struktur. A teraz o prototypach funkcji;
będziesz potrzebował dołączać inne pliki.
Są one wszystkie zapisane w folderze \masm32\include.
W naszym
przykładzie powyżej, wywołujemy funkcję eksportowaną przez kernel32.dll, więc będziemy potrzebować
dołaczyć tę funkcję
z kernel32.dll. Tym plikiem jest kernel32.inc. Jeżeli otworzysz go z edytora tekstu,
zobaczysz, że jest pełen prototypów funkcji dla kernel32.dll. Jeżeli nie dołaczysz
kernel32.inc, możesz nadal wywoływać ExitProcess ale tylko według prostej składni
call. Nie będzieszmógł skorzystać z funkcjiinvoke
. Różnica jest następująca: W celu wywołania funkcji invok, musisz
dodać prototyp funkcji gdzieś w kodzie źródłowym. W powyższym przykładzie,
jeżli nie dołączysz kernel32.inc, możesz zdefiniować prototyp funkcji dla
ExitProcess gdziekolwiek w kodzie źródłowym powyżej polecenia invoke i to będzie działać.
Pliki dołączane poleceniem includes służą oszczędzaniu pracy potrzebnej na wpisywanie prototypów
więc należy z nich korzystać jak najczęściej.
Teraz zajmiemy się nową dyrektywą
, includelib.
includelib nie działa tak samo jak include.
To tylko sposób na powiedzienie assembler'owi, że program będzie używał bibliotek.
Kiedy assembler widzi dyrektywę includelib, dodaję komendę do obiektu z pliku, tak, że
linker wie, że program potrzebuje importować biblioteki w celu przyłączenia do pliku
. Nie musisz jednak się męczyć i używać includelib. Możesz wyspecyfikować
nazwy importowanych bibliotek w komendzie wywołującej linker, lecz wierz mi
, to trudne i linia komendy może zawierać tylko 128 znaków.
Teraz zapisz przykład pod nazwą msgbox.asm. Przyjmujemy, że ml.exe jest w ścieżce dostępu, zasembluj msgbox.asm z parametrami:
Następnie przystąp do łączenia (link) :
/SUBSYSTEM:WINDOWS Powyższe polecenia informują linker jakiego rodzaju plikiem wykonywalnym jest ten program.Linker czyta plik obiektowy i dokonuje jego powiązania z importowanymi bibliotekami. Kiedy ten proces jest zakończony uzyskujesz plik msgbox.exe.
/LIBPATH:<ścieżka do importowanej biblioteki> mówi linkerowi gdzie są importowane biblioteki. Jeżeli używasz MASM32, będą one w folderze MASM32\lib.
Teraz już masz msgbox.exe. Dalej, uruchom go. Przekonasz się, że niczego on nie wykonuje. Cóż, jeszcze niczego interesującego nie wplisaliśmy do niego. Niemniej jest to windows'owy program. I zobacz jaki ma rozmiar! Na moim PC, to jest 2,560 bytes.
Następnie wprowadzimy okienko informacyjne (message box). Prototyp funkcji dla tego okienka jast następujący:
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd jest uchwytem (handle) do okna macierzystego (parent window). Możesz uważać uchwyt za numer, który reprezentuje okno do którego się odwołujesz . Jego wartość nie jest ważna. Musisz tylko pamiętać, że reprezentuje okno. Kiedy zechcesz cokolwiek zrobić z tym oknem, musisz się do niego odwołać poprzez jego uchwyt.Zmodyfikuj plik msgbox.asm aby uzyskać okienko informacyjne.
lpText jest wskaźnikiem do tekstu, jaki chcesz aby wyswietlił się w obszarze roboczym (client area) tego okienka. Wskaźnik jest tak naprawdę adresem czegoś. Wskaźnik do łancucha tekstu ==adres tego łańcucha.
lpCaption jest wskaźnikiem do opisu (caption) tego okienka informacyjnego
uType określa ikonę oraz liczbę i typ przycisków okienka
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption
db "Iczelion Tutorial No.2",0
MsgBoxText
db "Win32 Assembly is Great!",0
.code
start:
invoke MessageBox,
NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess,
NULL
end start
Zasembluj i uruchom go . Zobaczysz okienko informacyjne wyświetlające tekst "Win32 Assembly is Great!".
Popatrz jeszcze raz na kod
źródłowy.
Definiujemy dwa
zakończone zerem łańcuchy w sekcji .data. Pamiętaj, że każdy ciąg znaków ANSI
w Windows musi być zakończony znakiem NULL (0 szesnastkowo).
Używamy dwóch stałych,
NULL i MB_OK. Te stałe są opisane w windows.inc. Możesz odnosić się do nich
przez nazwę zamiast przez wartość. To poprawia czytelność
twojego kodu źródłowego.
addr
ten operator jest używany do przekazania adresu etykiety do funkcjii.
To działa wyłącznie w kontekście dyrektywy invoke.
Nie możesz na przykład używać go do oznaczenia adresu etykiety do rejestru/zmiennej.
Możesz użyć offset
zamiast addr w powyższym przykładzie. Jednak, jest kilka różnic
pomiędzy nimi:
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OKMASM wygeneruje błąd. Jeżeli użyjesz offset zamiast addr w powyższym kodzie, MASM zasembluje go bezbłędnie.
......
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is Great!",0
lea eax, LocalVar
push eax
Ponieważ lea
może określać adres etykiety podczas uruchamiania, działa prawidłowo.