3. diel - Hello world v ASM vo Windows
V minulej lekcii, Inštalácia kompilátora assembleri vo Windows , sme si nainštalovali kompilátor a vytvorili prvú zatiaľ prázdny ASM projekt.
V dnešnom ASM tutoriálu vytvoríme prvý program v assembleri pre Windows.
Pôjde o 32 i 64-Bito aplikáciu používajúci MessageBox
a
konzolu k výpisu textu.
Hello world v ASM - Desktopová aplikácia
Pri vytváraní nového projektu sme si zvolili, či chceme konzolovú alebo
desktopovú aplikáciu. To môžeme kedykoľvek zmeniť aj vo vlastnostiach
projektu. Konkrétne v sekcii Linker / System je položka
subsystému. U konzolové aplikácie je tam nastavené Console
(/SUBSYSTEM:CONSOLE
). U desktopové aplikácie tam je Windows
(/SUBSYSTEM:WINDOWS
).
32-bitová aplikácia
Najskôr si ukážeme 32-bitovou desktopovú aplikáciu a vysvetlíme si
základnú štruktúru programu. Do .asm
súboru vložíme
nasledujúci kód, ktorý zobrazí text Hello, world !
ako
vyskakovacie okienko MessageBox
:
.586 .model flat, stdcall include windows.inc .data message db "Hello, world !",0 .code main proc invoke MessageBox, 0, addr message, addr message, MB_OK ret main endp end main
Kód si teraz podrobne rozoberieme.
Procesor a model
Na prvom riadku je uvedený procesor, pre ktorý je aplikácia určená, čo
je .586
:
.586 .model flat, stdcall
Ak chceme, aby aplikácia bežala tiež na procesoroch z deväťdesiatych
rokov, môžeme napísať .386
alebo .486
. Potom ale
nebudeme môcť používať niektoré novšie inštrukcie.
Na druhom riadku je pamäťový model flat
a volacie konvencie
stdcall
. Toto nastavenie používajú systémové funkcie Windows,
takže tu nemáme na výber.
include
Ak používame systémové funkcie Windows, musíme si do programu vložiť
windows.inc
:
include windows.inc
Bez funkcií operačného systému by sme o vypísanie texte museli
požiadať napr. Priamo BIOS, čo si tiež ukážeme. Teraz sa nám ale funkcia
pre výpis textu užívateľovi bude hodiť:) Cestu k windows.inc
nemusíme písať, pretože sme ju už zadali v nastavení kompilátora
parametrom /I
.
Vo väčších projektoch môžeme používať svoje vlastné
.inc
súbory. To je vhodné pre definícia spoločných konštánt
alebo makier. Tie potom môžeme vložiť do niekoľkých ASM súborov. Ale o
tom až neskôr v kurze.
.data
, .code
Direktívy .data
a .code
rozdeľujú program na
dáta a kód:
.data message db "Hello, world !",0 .code
To je dôležité hlavne aby fungovalo takzvané "Zamedzenie spustenia údajov" (Data Execution Prevention, DEP). To je ochrana proti vírusom, ktoré sa predtým šírili v obrázkoch alebo dokonca v MP3 súboroch. Ak používateľ otvoril súbor v aplikácii, ktorá mala bezpečnostnú chybu pretečeniu zásobníka (buffer overflow), potom inštrukcie RET skočila do dátovej sekcie, kde sa spustil vírus.
V dnešnej dobe to už nie je problém, pretože aj keby sme si do dátovej sekcie zapísali nejaké inštrukcie, nemôžeme je spustiť (spôsobí to výnimku a aplikácie spadne). Vo 32-bitových aplikáciách si môžete DEP vypnúť v nastaveniach Linker -> Advanced, ale rozhodne to nikdy nerobte.
Za behu programu nemôžeme zapisovať inštrukcie ani do sekcie
.code
, pretože tá je len na čítanie. To znamená, že program
nemôže sám seba modifikovať. Hoci v assembleri programujeme na najnižšej
úrovni priamo s inštrukciami procesora, nemôžeme si robiť čo chceme a
nemožno obísť bezpečnostné obmedzenia operačného systému (ak píšeme
program pre neho).
Ak niektoré dáta nepotrebujeme meniť, je lepšie umiestniť
ich do sekcie .const
, ktorá je len na čítanie. Ak dáta
nepotrebujeme inicializovať, ale iba pre nich vyhradiť miesto v pamäti,
použijeme sekcii .data?
.
Direktívy db
(Define Byte) a dw
Define Word)
Direktívou db
sa zadávajú dáta typu byte
,
definujeme teda premennú ktorá môže obsahovať napr. 8-bitové číslo (teda
8 jedničiek / núl, jeden bajt) alebo texty v ANSI kódovanie (v strednej
Európe je kódovanie Windows-1250). Kompiler potom 1 bajt založí pre každý
znak, čo je aj náš prípad. Viac hodnôt sa oddeľuje čiarkami, tu nulou
0
ukončíme reťazec.
Vo viacjazyčných aplikáciách sa používa Unicode a
všetky texty sa dávajú zvyčajne do resources. Môžeme ale použiť aj
direktívu dw
, ktorá potom v pamäti vyhradí 2 bajty, viď
ďalej.
Ďalšie kódovanie
Hoci to naša ukážka pre jednoduchosť neobsahuje, v UASM môžeme bez problémov používať Unicode. Keď je ASM súbor v kódovaní UTF-8, zapíše sa nasledovne:
option literals:on message dw "čeština",0
V kompilátora ASMC si navyše môžeme určiť, v akom kódovanie je ASM súbor:
option wstring:on option codepage:CP_UTF8 message dw "čeština",0
V MASM od Microsoftu možno Unicode zadať len ťažko a to ako hexadecimálne hodnoty jednotlivých znakov:
message dw 10dh,'e',161h,'t','i','n','a', 0
Všetky systémové funkcie pracujúce s Unicode textom majú na konci
písmeno W
, napr. MessageBoxW
. Podobne funkcie
pracujúce s 8-bitovým (ANSI) kódovaním majú na konci písmeno
A
, napr. MessageBoxA
. V programoch sa zvyčajne suffix
A
nepíše, pretože ho tam automaticky doplní kompilátor. Ak
chceme, aby kompilátor dopĺňal suffix W
, do parametrov
kompilátora pridáme /D UNICODE
.
proc
, endp
A máme tu hlavnú funkciu programu, podobne ako je tomu v mnohých
vyšších programovacích jazykoch. Funkcia čiže procedúry sa zapisujú tak,
že na začiatku funkcia je direktíva proc
a na konci funkcia je
endp
:
main proc invoke MessageBox, 0, addr message, addr message, MB_OK ret main endp end main
Pred oboma direktívami je názov funkcie. Za proc
môžu ešte
nasledovať parametre funkcie.
Inštrukcia ret
(return)
Na konci funkcia je inštrukcia ret
.
Vo vyšších programovacích jazykoch sme si zvykli, že sa
na konci funkcie nemusia písať return, ale v ASM nesmiete na
ret
zabudnúť.
invoke
Direktíva invoke
vykoná funkciu so zadanými parametrami:
main proc invoke MessageBox, 0, addr message, addr message, MB_OK ret main endp
Kompilátor ju preloží na inštrukciu call
a parametre vloží
na zásobník inštrukciami push
. Operátor addr
vracia adresu globálne alebo lokálne premenné.
Posledný parameter funkcie MessageBox
určuje, aké budú pod
textom tlačidla. Môžete skúsiť napríklad MB_YESNOCANCEL
. Kód
tlačidla, ktoré používateľ stlačil, potom bude v registri
eax
.
Registre sú už predzaložené "premennej" priamo v procesore a práca s nimi je rýchlejší, než keby sme pristupovali kvôli všetkému do pamäte RAM. Cez registre budeme pracovať napr. S množstvom parametrov a návratových hodnôt.
MessageBox je štandardné funkcie Windows API a pravdepodobne ste s ním už pracovali cez nejaký vyšší programovací jazyk.
end
Na konci každého ASM súboru musí byť direktíva end
. Za
ňou môže byť nepovinne názov hlavnej funkcie, ktorou sa aplikácia
spúšťa:
end main
Hlavné funkciu možno zadať tiež v nastavení projektu - Linker -> Advanced -> Entry Point.
64-bitová aplikácia
Skúsme si teraz vytvoriť aplikáciu ako 64-bitovú:
option win64:2 include windows.inc .data message db "Hello, world !",0 .code main proc invoke MessageBox, 0, addr message, addr message, MB_OK ret main endp end main
V 64-bitových aplikáciách nie je na začiatku programu typ procesora a
model. Namiesto toho je tu direktíva option win64:2
, ktorá
určuje, ako sa majú kompilovať funkcie. Ak by sme na option
zabudli alebo nastaviť hodnotu menšiu ako 2
, potom by program
spadol, pretože by zásobník nebol zarovnaný na násobok 16
.
Prečo tomu tak je si vysvetlíme v lekcii o funkciách.
Kompilátor MASM od Microsoftu nevie ani takto jednoduchý program skompilovať. Preto musíme používať kompilátor UASM.
Hello world v ASM - Konzolová aplikácia
Do tretice si vytvoríme 64-bitovú ukážku tej istej aplikácie, ktorá bude však text vypisovať do štandardnej konzoly:
option win64:2 include windows.inc .data message db "Hello, world !" .code main proc invoke GetStdHandle, STD_OUTPUT_HANDLE invoke WriteConsole, rax, addr message, sizeof message, NULL, NULL ret main endp end main
Handle
Keď chceme používať nejaký systémový objekt vo Windows, napr. Okno
konzoly, musíme najskôr získať jeho handle. Funkcia
GetStdHandle
vracia handle na štandardný vstup alebo výstup.
Návratová hodnota je vždy v registri rax
.
Handle potom odovzdáme ako prvý parameter funkcie
WriteConsole
. Operátor sizeof
zistí dĺžku textu.
Všimnite si, že text nemusí končiť nulou ako u MessageBoxu
.
Štvrtý parameter je nepovinný, namiesto neho sme dali NULL
. To
je rovnaké ako 0
, ale pre lepšiu čitateľnosť je lepšie
používať NULL
, aby bolo vidieť, že je to ukazovateľ
(adresa).
V konzole sa používa OEM kódovanie (v strednej Európe je code page 852).
32-bitová verzia
32-bitová konzolová aplikácie by vyzerala podobne. Líši sa akurát
prvým riadkom a namiesto registra rax
by bol register
eax
. Keďže je 32-bitová architektúra zastaraná, tak nám na
ňu v lekcii jeden príklad bohato stačí, prípadne ho nájdete k stiahnutiu v
prílohe.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 36x (10.44 kB)
Aplikácia je vrátane zdrojových kódov v jazyku ASM