Assembly helloworld
Windows 10 operációs rendszer alatt
Sziasztok!
Pár hete kedvet kaptam mélyre merülni az assembly nyelvben leginkább abból a célból, hogy mélységeiben megismerjem a számítógép és az operációs rendszerek működését. Belevetettem magam az interneten elérhető magyar irodalomba a témában, de elkeserítő volt az amit találtam. Arra jó volt, hogy az assembly nyelv működését megértsem, de ahhoz már kevés, hogy egy "Hello World" szöveget a képernyőn megjelenítsek. A probléma ugyanis az, hogy ezen könyvek döntő többsége elavultak és MS-DOS programozásáról szólnak, ahol sokkal egyszerűbb az élet assembly szempontból mint manapság. Tehát a nyelv 'megismerése' után, úgy gondoltam írok egy helloworld programot. (aki új lenne a programozásban, a helloworld mindig egy hosszú utazás kezdetét jelenti, amikor is a programozó egyre nehezedő problémákat old meg egy új nyelven) Tehát ott tartottam, hogy megpróbáltam írni egy helloworld programot ami sajnos nem jött össze, hiszen a könyvben leírt módszerek nem működtek. A régi MS-DOS-os módszer:
org 100h mov dx,msg mov ah,9 int 21h mov ah,4Ch int 21h msg db 'Hello, World!',0Dh,0Ah,'$'
Tisztán látszik, hogy ez a program megszakításokat (interrupts), használ, erre utal az int 21h utasítás (MS-DOS), és mivel az ah regiszterbe 9-et raktunk jeleztünk a szándékunkat, hogy a standard kimenetre akarunk szöveget (string-et) írni, méghozzá dx regiszterbe tett (mov dx, msg) memóriahelytől kezdődően. Alapértelmezett módon $ karakterig bezáródóan fogja kiírni a karaktereket. A következő megszakításnál pedig a program bezárását kérjük (mov ah, 4Ch). Az org 100h, parancs szorulhat még némi magyarázatra, amibe most nem megyünk bele annyira, elég annyit tudni, hogy a .COM programok 100h hexadecimális memóriahelynél kezdődnek el.
Problémánk ezzel: A Windows 10 operációs rendszer nem képes futtatni a .com fájlokat. Illetve tudtommal nem támogatja a megszakításokat sem, hanem az úgynevezett winapi hívások segítségével érjük el többek között azokat a funkciókat amiket anno MS-DOS alatt a megszakításokkal elértünk. Na ez volt az a rész, amiről a magyar irodalomban semmit sem találtam és ez okból döntöttem úgy, hogy blogot írok a próbálkozásaimról. :)
Verejtékes munkával, rengeteg sikertelen próbálkozás után ez lett a kód ami működik is:
global _main
extern _GetStdHandle@4
extern _WriteFile@20
extern _ExitProcess@4
section .text
_main:
; DWORD bájt, lokális változó a WriteFile függvény &bytes paraméteréhez
mov ebp, esp
sub esp, 4
; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
push -11
call _GetStdHandle@4
mov ebx, eax
; WriteFile( hstdOut, message, length(message), &bytes, 0);
push 0
lea eax, [ebp-4]
push eax
push (message_end - message)
push message
push ebx
call _WriteFile@20
; ExitProcess(0)
push 0
call _ExitProcess@4
; never here
hlt
message:
db 'Hello World'
message_end:
A fordítása pedig: (a fejlesztői környezet telepítéséről itt olvashattok bővebben)
nasm -fwin32 helloworld.asm
gcc helloworld.obj -o helloworld.exe
A kód működését most nem részletezném, szerintem nem túl nehéz megérteni ha vannak alapvető ismereteink az assembly nyelvvel kapcsolatosan. A lényeges dolog az, hogy a winapi függvény hívásokat végzünk, a paramétereket pedig veremben adjuk át. A _GetStdHandle@4 hívással -11-es paraméterrel megkapjuk "a standard kimenet kezelőjét" (igen, elég hülyén hangzik magyarul), aminek a segítségével, _WriteFile@20 hívással a kimenetre írjuk az üzenetünket, majd az _ExitProcess@4 segítségével kilépünk a programból 0-ás visszatérési értékkel.
Ha nem vagyunk ilyen mazochisták ugyanezt az eredményt ennyivel is elérhetjük, azzal, hogy a C könyvtár printf függvényét használjuk, persze ebben az esetben elveszti a szépségét az, hogy közvetlenül a winapi-t használtuk volna:
global _main
extern _printf
[section] .text
_main:
push message
call _printf
add esp, 4
ret
message:
db 'Hello World', 0
Fordítása pedig: (a fejlesztői környezet telepítéséről itt olvashattok bővebben)
nasm -fwin32 helloworld.asm
gcc helloworld.obj -o helloworld.exe
Fontos, hogy abban az esetben, ha gcc-vel fordítjuk az objektumunkat gépi kódra, fontos, hogy az assembly fájlban a _main címkénél fog kezdődni a programunk, és ha nem talál ilyen belépési pontot a fordítás sem lesz sikeres.
Összefoglalva tehát, most boldogság van, mert sikeresen kiírtuk a Hello World szöveget a standard kimenetre, winapi közvetlen hívásával és a C könyvtár használatával is. Mérésem szerint a direkt winapi hívásos módszer gyorsabb mint a C könyvtáras módszer. (bár a különbség nem sok, 20000 futtatás esetén 4 másodperc :)
Remélem tetszett az írás, a jövőben igyekszek egy gyors összefoglalót írni az assembly nyelvről is, illetve picit komolyabb, összetettebb alkalmazásokat is írni, akár LINUX alá is.
Szabó Dávid