IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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 34x (10.44 kB)
Aplikace je včetně zdrojových kódů v jazyce ASM

 

Předchozí článek
Instalace kompilátoru assembleru ve Windows
Všechny články v sekci
Základy assembleru
Přeskočit článek
(nedoporučujeme)
Assembler - Vytvoření NASM projektu, registry a přerušení
Článek pro vás napsal Petr Laštovička
Avatar
Uživatelské hodnocení:
13 hlasů
Autor se věnuje vývoji webových aplikací v ASP.NET a aplikací pro Windows v C++ nebo C#.
Aktivity