Lekce 3 - Hello world v ASM ve Windows
V minulé lekci, Instalace kompilátoru assembleru ve Windows, jsme si nainstalovali kompilátor a vytvořili první zatím prázdný ASM projekt.
V dnešním ASM tutoriálu vytvoříme první program v assembleru pro
Windows. Půjde o 32 i 64-bitou aplikaci používající MessageBox
a konzoli k výpisu textu.
Hello world v ASM - Desktopová aplikace
Při vytváření nového projektu jsme si zvolili, jestli chceme konzolovou
nebo desktopovou aplikaci. To můžeme kdykoli změnit také ve vlastnostech
projektu. Konkrétně v sekci Linker / System je položka
SubSystem. U konzolové aplikace je tam nastaveno Console
(/SUBSYSTEM:CONSOLE
). U desktopové aplikace tam je Windows
(/SUBSYSTEM:WINDOWS
).
32-bitová aplikace
Nejdříve si ukážeme 32-bitovou desktopovou aplikaci a vysvětlíme si
základní strukturu programu. Do .asm
souboru vložíme
následující kód, který zobrazí text Hello, world !
jako
vyskakovací okénko 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 nyní podrobně rozebereme.
Procesor a model
Na prvním řádku je uveden procesor, pro který je aplikace určena, což
je .586
:
.586 .model flat, stdcall
Pokud chceme, aby aplikace běžela také na procesorech z devadesátých
let, můžeme napsat .386
nebo .486
. Pak ale nebudeme
moci používat některé novější instrukce.
Na druhém řádku je paměťový model flat
a volací konvence
stdcall
. Toto nastavení používají systémové funkce Windows,
takže zde nemáme na výběr.
include
Pokud používáme systémové funkce Windows, musíme si do programu vložit
windows.inc
:
include windows.inc
Bez funkcí operačního systému bychom o vypsání textu museli požádat
např. přímo BIOS, což si také ukážeme. Nyní se nám ale funkce pro
výpis textu uživateli bude hodit Cestu k windows.inc
nemusíme psát, protože jsme ji už
zadali v nastavení kompilátoru parametrem /I
.
Ve větších projektech můžeme používat své vlastní
.inc
soubory. To je vhodné pro definice společných konstant nebo
maker. Ty potom můžeme vložit do několika ASM souborů. Ale o tom až dále
v kurzu.
.data
, .code
Direktivy .data
a .code
rozdělují program na data
a kód:
.data message db "Hello, world !",0 .code
To je důležité hlavně aby fungovalo takzvané "Zabránění spuštění dat" (Data Execution Prevention, DEP). To je ochrana proti virům, které se dříve šířily v obrázcích nebo dokonce v MP3 souborech. Pokud uživatel otevřel soubor v aplikaci, která měla bezpečnostní chybu přetečení zásobníku (buffer overflow), pak instrukce RET skočila do datové sekce, kde se spustil virus.
V dnešní době to už není problém, protože i kdybychom si do datové sekce zapsali nějaké instrukce, nemůžeme je spustit (způsobí to výjimku a aplikace spadne). Ve 32-bitových aplikacích si můžete DEP vypnout v nastavení Linker -> Advanced, ale rozhodně to nikdy nedělejte.
Za běhu programu nemůžeme zapisovat instrukce ani do sekce
.code
, protože ta je pouze pro čtení. To znamená, že program
nemůže sám sebe modifikovat. Přestože v assembleru programujeme na
nejnižší úrovni přímo s instrukcemi procesoru, nemůžeme si dělat co
chceme a nelze obejít bezpečnostní omezení operačního systému (pokud
píšeme program pro něj).
Pokud některá data nepotřebujeme měnit, je lepší umístit
je do sekce .const
, která je pouze pro čtení. Pokud data
nepotřebujeme inicializovat, ale pouze pro ně vyhradit místo v paměti,
použijeme sekci .data?
.
Direktivy db
(Define Byte) a dw
Define Word)
Direktivou db
se zadávají data typu byte
,
definujeme tedy proměnnou která může obsahovat např. 8-bitové číslo
(tedy 8 jedniček/nul, jeden bajt) nebo texty v ANSI kódování (ve střední
Evropě je kódování Windows-1250). Kompiler poté 1 bajt založí pro každý
znak, což je i náš případ. Více hodnot se odděluje čárkami, zde nulou
0
ukončíme řetězec.
Ve vícejazyčných aplikacích se používá Unicode a
všechny texty se dávají obvykle do resources. Můžeme ale použít také
direktivu dw
, která pak v paměti vyhradí 2 bajty, viz dále.
Další kódování
Ačkoli to naše ukázka pro jednoduchost neobsahuje, v UASM můžeme bez problémů používat Unicode. Když je ASM soubor v kódování UTF-8, zapíše se takto:
option literals:on message dw "čeština",0
V kompilátoru ASMC si navíc můžeme určit, v jakém kódování je ASM soubor:
option wstring:on option codepage:CP_UTF8 message dw "čeština",0
V MASM od Microsoftu lze Unicode zadat jen obtížně a to jako hexadecimální hodnoty jednotlivých znaků:
message dw 10dh,'e',161h,'t','i','n','a', 0
Všechny systémové funkce pracující s Unicode textem mají na konci
písmeno W
, např. MessageBoxW
. Podobně funkce
pracující s 8-bitovým (ANSI) kódováním mají na konci písmeno
A
, např. MessageBoxA
. V programech se obvykle suffix
A
nepíše, protože ho tam automaticky doplní kompilátor. Pokud
chceme, aby kompilátor doplňoval suffix W
, do parametrů
kompilátoru přidáme /D UNICODE
.
proc
, endp
A máme zde hlavní funkci programu, podobně jako je tomu v mnoha vyšších
programovacích jazycích. Funkce neboli procedury se zapisují tak, že na
začátku funkce je direktiva proc
a na konci funkce je
endp
:
main proc invoke MessageBox, 0, addr message, addr message, MB_OK ret main endp end main
Před oběma direktivami je název funkce. Za proc
mohou ještě
následovat parametry funkce.
Instrukce ret
(RETurn)
Na konci funkce je instrukce ret
.
Ve vyšších programovacích jazycích jsme si zvykli, že
se na konci funkce nemusí psát return, ale v ASM nesmíte na
ret
zapomenout.
invoke
Direktiva invoke
vykoná funkci se zadanými parametry:
main proc invoke MessageBox, 0, addr message, addr message, MB_OK ret main endp
Kompilátor ji přeloží na instrukci call
a parametry vloží
na zásobník instrukcemi push
. Operátor addr
vrací
adresu globální nebo lokální proměnné.
Poslední parametr funkce MessageBox
určuje, jaká budou pod
textem tlačítka. Můžete zkusit třeba MB_YESNOCANCEL
. Kód
tlačítka, které uživatel stiskl, potom bude v registru eax
.
Registry jsou již předzaložené "proměnné" přímo v procesoru a práce s nimi je rychlejší, než kdybychom přistupovali kvůli všemu do paměti RAM. Přes registry budeme pracovat např. se spoustou parametrů a návratových hodnot.
MessageBox je standardní funkce Windows API a pravděpodobně jste s ním již pracovali přes nějaký vyšší programovací jazyk.
end
Na konci každého ASM souboru musí být direktiva end
. Za ní
může být nepovinně název hlavní funkce, kterou se aplikace spouští:
end main
Hlavní funkci lze zadat také v nastavení projektu - Linker -> Advanced -> Entry Point.
64-bitová aplikace
Zkusme si nyní vytvořit aplikaci jako 64bitovou:
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 aplikacích není na začátku programu typ procesoru a model.
Místo toho je zde direktiva option win64:2
, která určuje, jak se
mají kompilovat funkce. Pokud bychom na option
zapomněli nebo
nastavili hodnotu menší než 2
, pak by program spadl, protože by
zásobník nebyl zarovnán na násobek 16
. Proč tomu tak je si
vysvětlíme v lekci o funkcích.
Kompilátor MASM od Microsoftu neumí ani takto jednoduchý program zkompilovat. Proto musíme používat kompilátor UASM.
Hello world v ASM - Konzolová aplikace
Do třetice si vytvoříme 64bitovou ukázku té samé aplikace, která bude ovšem text vypisovat do standardní konzole:
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
Když chceme používat nějaký systémový objekt ve Windows, např. okno
konzole, musíme nejdříve získat jeho handle. Funkce
GetStdHandle
vrací handle na standardní vstup nebo výstup.
Návratová hodnota je vždy v registru rax
.
Handle pak předáme jako první parametr funkce WriteConsole
.
Operátor sizeof
zjistí délku textu. Všimněte si, že text
nemusí končit nulou jako u MessageBoxu
. Čtvrtý parametr je
nepovinný, místo něj jsme dali NULL
. To je stejné jako
0
, ale pro lepší čitelnost je lepší používat
NULL
, aby bylo vidět, že je to ukazatel (adresa).
V konzoli se používá OEM kódování (ve střední Evropě je code page 852).
32-bitová verze
32-bitová konzolová aplikace by vypadala podobně. Lišila by se akorát
prvním řádkem a místo registru rax
by byl registr
eax
. Jelikož je 32bitová architektura zastaralá, tak nám na ni
v lekci jeden příklad bohatě stačí, případně jej naleznete ke stažení
v příloze.
V příští lekci, Assembler - Vytvoření NASM projektu, registry a přerušení, si založíme ASM projekt a řekneme si co jsou to registry a přerušení.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 36x (10.44 kB)
Aplikace je včetně zdrojových kódů v jazyce ASM